{"record":{"id":"cafc44f5ee2c6cc8","repo":"aeron-io/aeron","slug":"already-in-snapshot-servicesnapshotloader","errorCode":null,"errorMessage":"already in snapshot","messagePattern":"already in snapshot","errorType":"console","errorClass":"ClusterException","httpStatus":null,"severity":"critical","filePath":"aeron-cluster/src/main/java/io/aeron/cluster/service/ServiceSnapshotLoader.java","lineNumber":102,"sourceCode":"            case SnapshotMarkerDecoder.TEMPLATE_ID:\n                snapshotMarkerDecoder.wrap(\n                    buffer,\n                    offset + MessageHeaderDecoder.ENCODED_LENGTH,\n                    messageHeaderDecoder.blockLength(),\n                    messageHeaderDecoder.version());\n\n                final long typeId = snapshotMarkerDecoder.typeId();\n                if (SNAPSHOT_TYPE_ID != typeId)\n                {\n                    throw new ClusterException(\"unexpected snapshot type: \" + typeId);\n                }\n\n                switch (snapshotMarkerDecoder.mark())\n                {\n                    case BEGIN:\n                        if (inSnapshot)\n                        {\n                            throw new ClusterException(\"already in snapshot\");\n                        }\n                        inSnapshot = true;\n                        appVersion = snapshotMarkerDecoder.appVersion();\n                        timeUnit = ClusterClock.map(snapshotMarkerDecoder.timeUnit());\n                        return Action.CONTINUE;\n\n                    case END:\n                        if (!inSnapshot)\n                        {\n                            throw new ClusterException(\"missing begin snapshot\");\n                        }\n                        isDone = true;\n                        return Action.BREAK;\n\n                    case SECTION:\n                    case NULL_VAL:\n                        break;\n                }","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-cluster/src/main/java/io/aeron/cluster/service/ServiceSnapshotLoader.java#L84-L120","documentation":"The loader is a state machine over SnapshotMarker frames: BEGIN opens the snapshot, SECTION streams entries, END closes it. A second BEGIN while inSnapshot is already true means the stream contains nested or duplicated snapshot start markers, which is invalid. The loader throws rather than silently overwriting state captured from the first BEGIN (appVersion, timeUnit).","triggerScenarios":"onFragment receives a SnapshotMarker with mark=BEGIN while the inSnapshot flag is already true, i.e. two BEGIN markers without an intervening END.","commonSituations":"Corrupted or concatenated recordings replayed as one snapshot stream, replay starting mid-recording then restarting from the beginning, or a recording tool that stitched snapshots together.","solutions":["Replay exactly one snapshot recording (a single BEGIN/END pair) during recovery","Re-take the snapshot to replace the corrupted/concatenated recording","Check archive replay offsets so the stream does not restart inside an already-open snapshot"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure exactly one snapshot recording is replayed per recovery\nif (recordings.length != 1) { throw new IllegalArgumentException(\"expected one snapshot recording, got \" + recordings.length); }","typeGuard":null,"tryCatchPattern":"try { loader.load(...); } catch (ClusterException e) { abortRecovery(e); }","preventionTips":["Never concatenate snapshot recordings","Replay each recording from position 0","Re-take snapshots rather than patching broken ones"],"tags":["aeron-cluster","snapshot","state-machine","corrupt-data"],"backgroundTag":"invalid-state-transition","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}