{"record":{"id":"0496fb07eee3c72e","repo":"alibaba/canal","slug":"buffersize-must-be-a-power-of-2","errorCode":null,"errorMessage":"bufferSize must be a power of 2","messagePattern":"bufferSize must be a power of 2","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"parse/src/main/java/com/alibaba/otter/canal/parse/inbound/EventTransactionBuffer.java","lineNumber":43,"sourceCode":"    private CanalEntry.Entry[]       entries;\n\n    private AtomicLong               putSequence   = new AtomicLong(INIT_SQEUENCE); // 代表当前put操作最后一次写操作发生的位置\n    private AtomicLong               flushSequence = new AtomicLong(INIT_SQEUENCE); // 代表满足flush条件后最后一次数据flush的时间\n\n    private TransactionFlushCallback flushCallback;\n\n    public EventTransactionBuffer(){\n\n    }\n\n    public EventTransactionBuffer(TransactionFlushCallback flushCallback){\n        this.flushCallback = flushCallback;\n    }\n\n    public void start() throws CanalStoreException {\n        super.start();\n        if (Integer.bitCount(bufferSize) != 1) {\n            throw new IllegalArgumentException(\"bufferSize must be a power of 2\");\n        }\n\n        Assert.notNull(flushCallback, \"flush callback is null!\");\n        indexMask = bufferSize - 1;\n        entries = new CanalEntry.Entry[bufferSize];\n    }\n\n    public void stop() throws CanalStoreException {\n        putSequence.set(INIT_SQEUENCE);\n        flushSequence.set(INIT_SQEUENCE);\n\n        entries = null;\n        super.stop();\n    }\n\n    public void add(List<CanalEntry.Entry> entrys) throws InterruptedException {\n        for (CanalEntry.Entry entry : entrys) {\n            add(entry);","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/EventTransactionBuffer.java#L25-L61","documentation":"Thrown as IllegalArgumentException by EventTransactionBuffer.start because the ring buffer implementation uses an index mask (bufferSize - 1) for O(1) slot addressing, which only works when bufferSize is a power of two. Integer.bitCount(bufferSize) != 1 is the power-of-two check; any other size breaks the masking logic.","triggerScenarios":"EventTransactionBuffer.start() runs when the parser starts the transaction buffer. If canal.instance.transaction.buffer.size (the bufferSize) is not 1, 2, 4, 8, ... the check fails. The mask indexMask = bufferSize - 1 and CanalEntry.Entry[bufferSize] array rely on the power-of-two invariant.","commonSituations":"A user sets canal.instance.transaction.buffer.size to a non-power-of-two value like 1000, 2048 is fine but 2000 is not, or leaves a custom value from tuning. Default is typically a power of two; overriding it incorrectly triggers this at startup.","solutions":["Set canal.instance.transaction.buffer.size to a power of two (e.g. 1024, 2048, 4096, 8192).","If unsure, remove the override to use the default power-of-two value.","When sizing, round up to the next power of two rather than a round decimal."],"exampleFix":"# before\ncanal.instance.transaction.buffer.size = 2000\n\n# after\ncanal.instance.transaction.buffer.size = 2048","handlingStrategy":"validation","validationCode":"static int nextPow2(int n) { return n <= 0 ? 1 : Integer.highestOneBit((n - 1) << 1 | 1); }\nint safe = (Integer.bitCount(bufferSize) == 1) ? bufferSize : nextPow2(bufferSize);\nbuffer.setBufferSize(safe);","typeGuard":"boolean isPow2(int n) { return n > 0 && (n & (n - 1)) == 0; }","tryCatchPattern":null,"preventionTips":["Set canal.instance.transaction.buffer.size to a power of two.","Round buffer sizes up to the next power of two when tuning.","Validate config in a unit test that asserts power-of-two."],"tags":["config","buffer","power-of-two","startup"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}