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
- Ensure each group sink thread follows the strict await(event) -> store -> clear(event) sequence without skipping steps.
- Avoid sharing a single TimelineTransactionBarrier across more threads than the configured group size.
- On this exception, treat the pipeline as poisoned: reset the instance (stop/start) to restore txState to 0 rather than continuing.
- 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
- Run exactly one sink thread per group slot to honor the barrier's single-writer design.
- Always pair await(event) with clear(event); never skip clear for a permitted event.
- Keep canal version current to get interrupt/reset race fixes.
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
- state is not correct in transaction
- mq get/ack not support concurrent & async ack
- mq get/ack not support concurrent & async ack
- mq get/ack not support concurrent & async ack
- socket has Interrupted !
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/d9f7aff1a2539aef.
Report an issue: GitHub.