alibaba/canal · critical · CanalSinkException

state is not correct in transaction

Error message

state is not correct in transaction

What it means

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.

Source

Thrown at sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineTransactionBarrier.java:72

        }
    }

    public void clear(Event event) {
        super.clear(event);

        // 应该先判断2,再判断是否是事务尾,因为事务尾也可以导致txState的状态为2
        // 如果先判断事务尾,那么2的状态可能永远没机会被修改了,系统出现死锁
        // CanalSinkException被注释的代码是不是可以放开??我们内部使用的时候已经放开了,从代码逻辑的分析上以及实践效果来看,应该抛异常
        if (txState.intValue() == 2) {// 非事务中
            boolean result = txState.compareAndSet(2, 0);
            if (result == false) {
                throw new CanalSinkException("state is not correct in non-transaction");
            }
        } else if (isTransactionEnd(event)) {
            inTransaction.set(false); // 事务结束并且已经成功写入store,清理标记,进入重新排队判断,允许新的事务进入
            boolean result = txState.compareAndSet(1, 0);
            if (result == false) {
                throw new CanalSinkException("state is not correct in transaction");
            }
        }
    }

    protected boolean isPermit(Event event, long state) {
        if (txState.intValue() == 1 && inTransaction.get()) { // 如果处于事务中,直接允许通过。因为事务头已经做过判断
            return true;
        } else if (txState.intValue() == 0) {
            boolean result = super.isPermit(event, state);
            if (result) {
                // 可能第一条送过来的数据不为Begin,需要做判断处理,如果非事务,允许直接通过,比如DDL语句
                if (isTransactionBegin(event)) {
                    if (txState.compareAndSet(0, 1)) {
                        inTransaction.set(true);
                        return true; // 事务允许通过
                    }
                } else if (txState.compareAndSet(0, 2)) { // 非事务保护中
                    // 当基于zk-cursor启动的时候,拿到的第一个Event是TransactionEnd

View on GitHub (pinned to 87be50e876)

Solutions

  1. 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.
  2. Verify binlog event ordering at the source (no gaps between TRANSACTIONBEGIN and TRANSACTIONEND entries).
  3. If triggered by HA failover, ensure the standby picks up cursor from ZooKeeper and the barrier reset path runs cleanly.
  4. Confirm the group sink thread count matches the configured groupSize to avoid duplicate transaction-end handling.
Defensive patterns

Strategy: fallback

Try / catch

try {
    barrier.clear(event);
} catch (CanalSinkException e) {
    if (e.getMessage().contains("in transaction")) {
        logger.error("transaction state corrupted, restarting instance", e);
        canalInstance.stop();
        canalInstance.start();
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14). Data as JSON: /api/errors/b66c59a8096750cb. Report an issue: GitHub.