alibaba/canal · critical · CanalSinkException

state is not correct in non-transaction

Error message

state is not correct in non-transaction

What it means

Thrown in TimelineTransactionBarrier.clear(event) when txState is 2 (non-transaction data in flight) but the CAS txState.compareAndSet(2, 0) fails — meaning another thread already mutated txState between the read and the CAS. This indicates a concurrent state corruption: clear() was invoked for a non-transactional event while another path changed the state, breaking the single-writer assumption of the barrier state machine.

Source

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

            super.await(event, timeout, unit);
        } catch (InterruptedException e) {
            // 出现线程中断,可能是因为关闭或者主备切换
            // 主备切换对应的事务尾会未正常发送,需要强制设置为事务结束,允许其他队列通过
            reset();
            throw e;
        }
    }

    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)) {

View on GitHub (pinned to 87be50e876)

Solutions

  1. Ensure each group sink thread follows the strict await(event) -> store -> clear(event) sequence without skipping steps.
  2. Avoid sharing a single TimelineTransactionBarrier across more threads than the configured group size.
  3. On this exception, treat the pipeline as poisoned: reset the instance (stop/start) to restore txState to 0 rather than continuing.
  4. Upgrade canal version if hitting this during master-slave switch, as the reset-on-interrupt path was added to handle exactly that race.
Defensive patterns

Strategy: fallback

Try / catch

try {
    barrier.clear(event);
} catch (CanalSinkException e) {
    if (e.getMessage().contains("non-transaction")) {
        logger.error("barrier state corrupted, resetting instance", e);
        canalInstance.stop();
        canalInstance.start(); // re-stream from last good cursor
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Two sink threads (group members) calling clear() concurrently on non-transactional events such that both read txState==2 but only one wins the CAS; an interrupt/reset racing with clear(); misuse of the barrier from multiple threads without the intended group coordination.

Common situations: Misconfigured group sink with overlapping threads writing the same barrier; a parser/restarter thread interrupting (reset()) during clear(); custom code that invokes the barrier outside the normal await->store->clear sequence.

Related errors


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