{"record":{"id":"b66c59a8096750cb","repo":"alibaba/canal","slug":"state-is-not-correct-in-transaction","errorCode":null,"errorMessage":"state is not correct in transaction","messagePattern":"state is not correct in transaction","errorType":"exception","errorClass":"CanalSinkException","httpStatus":null,"severity":"critical","filePath":"sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineTransactionBarrier.java","lineNumber":72,"sourceCode":"        }\n    }\n\n    public void clear(Event event) {\n        super.clear(event);\n\n        // 应该先判断2，再判断是否是事务尾，因为事务尾也可以导致txState的状态为2\n        // 如果先判断事务尾，那么2的状态可能永远没机会被修改了，系统出现死锁\n        // CanalSinkException被注释的代码是不是可以放开？？我们内部使用的时候已经放开了，从代码逻辑的分析上以及实践效果来看，应该抛异常\n        if (txState.intValue() == 2) {// 非事务中\n            boolean result = txState.compareAndSet(2, 0);\n            if (result == false) {\n                throw new CanalSinkException(\"state is not correct in non-transaction\");\n            }\n        } else if (isTransactionEnd(event)) {\n            inTransaction.set(false); // 事务结束并且已经成功写入store，清理标记，进入重新排队判断，允许新的事务进入\n            boolean result = txState.compareAndSet(1, 0);\n            if (result == false) {\n                throw new CanalSinkException(\"state is not correct in transaction\");\n            }\n        }\n    }\n\n    protected boolean isPermit(Event event, long state) {\n        if (txState.intValue() == 1 && inTransaction.get()) { // 如果处于事务中，直接允许通过。因为事务头已经做过判断\n            return true;\n        } else if (txState.intValue() == 0) {\n            boolean result = super.isPermit(event, state);\n            if (result) {\n                // 可能第一条送过来的数据不为Begin，需要做判断处理，如果非事务，允许直接通过，比如DDL语句\n                if (isTransactionBegin(event)) {\n                    if (txState.compareAndSet(0, 1)) {\n                        inTransaction.set(true);\n                        return true; // 事务允许通过\n                    }\n                } else if (txState.compareAndSet(0, 2)) { // 非事务保护中\n                    // 当基于zk-cursor启动的时候，拿到的第一个Event是TransactionEnd","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineTransactionBarrier.java#L54-L90","documentation":"Thrown in TimelineTransactionBarrier.clear(event) on the transaction-end branch: when isTransactionEnd(event) is true the code CAS-transitions txState from 1 (in-transaction) back to 0, and a failed CAS means another thread already altered txState. The transaction commit sequence (begin -> events -> end with matching clear calls) was violated, leaving the state machine inconsistent.","triggerScenarios":"A transaction-end event's clear() runs while txState is no longer 1 (e.g. it was reset to 0 by an interrupt or by a concurrent clear); missing or duplicated transaction begin/end events from the parser; master-slave switch interrupting mid-transaction so reset() clears txState before the end's clear() arrives.","commonSituations":"Binlog parser emits incomplete transaction (end without matching begin, or reordered events); failover/HA switch causing an interrupt+reset between begin and end; multiple group sink threads both seeing the same transaction end.","solutions":["On this exception, restart the CanalInstance to reset txState and re-stream from the last good position — the barrier state cannot be safely recovered mid-flight.","Verify binlog event ordering at the source (no gaps between TRANSACTIONBEGIN and TRANSACTIONEND entries).","If triggered by HA failover, ensure the standby picks up cursor from ZooKeeper and the barrier reset path runs cleanly.","Confirm the group sink thread count matches the configured groupSize to avoid duplicate transaction-end handling."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    barrier.clear(event);\n} catch (CanalSinkException e) {\n    if (e.getMessage().contains(\"in transaction\")) {\n        logger.error(\"transaction state corrupted, restarting instance\", e);\n        canalInstance.stop();\n        canalInstance.start();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Ensure binlog stream is complete and ordered (begin..end) — watch for parser gaps.","On HA failover, let the reset() path run fully before new events enter the barrier.","Match group sink thread count to groupSize to avoid duplicate transaction-end clears."],"tags":["sink","transaction","concurrency","state-machine","group-sink"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}