{"record":{"id":"2e553b0322b868c1","repo":"apache/seatunnel","slug":"vitess-cdc-reader-only-supports-one-active-streami","errorCode":null,"errorMessage":"Vitess CDC reader only supports one active streaming split.","messagePattern":"Vitess CDC reader only supports one active streaming split\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-cdc/connector-cdc-vitess/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/vitess/source/reader/VitessSourceReader.java","lineNumber":147,"sourceCode":"\n    @Override\n    public List<VitessSourceSplit> snapshotState(long checkpointId) {\n        synchronized (stateLock) {\n            if (runtime != null && !sourceSplits.isEmpty()) {\n                Map<String, byte[]> runtimeTableSchemas = runtime.snapshotTableSchemas();\n                if (runtimeTableSchemas != null && !runtimeTableSchemas.isEmpty()) {\n                    sourceSplits.get(0).setTableSchemas(runtimeTableSchemas);\n                }\n            }\n            return sourceSplits.stream().map(VitessSourceSplit::copy).collect(Collectors.toList());\n        }\n    }\n\n    @Override\n    public void addSplits(List<VitessSourceSplit> splits) {\n        synchronized (stateLock) {\n            if (!sourceSplits.isEmpty() && splits != null && !splits.isEmpty()) {\n                throw new IllegalStateException(\n                        \"Vitess CDC reader only supports one active streaming split.\");\n            }\n            if (splits != null) {\n                for (VitessSourceSplit split : splits) {\n                    sourceSplits.add(split.copy());\n                }\n            }\n            if (runtime == null && !sourceSplits.isEmpty()) {\n                // Start Debezium as soon as the split arrives so latest startup does not miss\n                // changes produced before the framework issues the first pollNext call.\n                runtime = new VitessStreamingRuntime(sourceConfig, sourceSplits.get(0));\n            }\n        }\n    }\n\n    @Override\n    public void handleNoMoreSplits() {}\n","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-cdc/connector-cdc-vitess/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/vitess/source/reader/VitessSourceReader.java#L129-L165","documentation":"Thrown by VitessSourceReader.addSplits when splits are added while the reader already holds an active split. Vitess CDC streams exactly one streaming split per reader, so concurrent or duplicate split assignment violates the reader's state invariant.","triggerScenarios":"Calling addSplits with a non-empty list while sourceSplits is non-empty — e.g. a restore path delivering splits twice, or an enumerator assigning a second split to an already-active reader (also observed in tests testCheckpointRestorePreservesOffsetsAndTableIdentity, testSpecificStartupUsesCapturedVgtid exercising restore).","commonSituations":"Checkpoint/restore delivering the same split again on top of an existing one; misconfigured enumerator parallelism sending more than one split to a reader; replay of addSplits after recovery.","solutions":["Ensure the split enumerator assigns at most one streaming split per reader (deregister/invalidate the reader after assignment)","On restore, clear or replace the existing split instead of appending (guard/replace state under the stateLock)","Update engine/checkpoint logic so addSplits is idempotent for the same split"],"exampleFix":"// before\nreader.addSplits(existingSplits); // called again after restore\n// after\nif (reader currently holds no split) { reader.addSplits(existingSplits); }","handlingStrategy":"try-catch","validationCode":"// caller-side: only hand each reader a split when it holds none\nif (readerState.hasActiveSplit()) {\n    throw new IllegalStateException(\"reader already has an active streaming split\");\n}","typeGuard":"boolean canAcceptSplits(VitessSourceReader r) { return !r.hasActiveSplit(); }","tryCatchPattern":"try {\n    reader.addSplits(splits);\n} catch (IllegalStateException e) {\n    // split already active — skip duplicate assignment or replace existing split\n}","preventionTips":["Make split assignment idempotent on checkpoint restore","Configure enumerator parallelism so each reader receives exactly one split","Track per-reader split assignment state in custom enumerators"],"tags":["cdc","vitess","reader-state","splits"],"backgroundTag":"invalid-state-transition","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}