aeron-io/aeron · critical · ClusterException

already in snapshot

Error message

already in snapshot

What it means

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

Solutions

  1. Replay exactly one snapshot recording (a single BEGIN/END pair) during recovery
  2. Re-take the snapshot to replace the corrupted/concatenated recording
  3. Check archive replay offsets so the stream does not restart inside an already-open snapshot
Defensive patterns

Strategy: validation

Validate before calling

// Ensure exactly one snapshot recording is replayed per recovery
if (recordings.length != 1) { throw new IllegalArgumentException("expected one snapshot recording, got " + recordings.length); }

Try / catch

try { loader.load(...); } catch (ClusterException e) { abortRecovery(e); }

Prevention

When it happens

Trigger: onFragment receives a SnapshotMarker with mark=BEGIN while the inSnapshot flag is already true, i.e. two BEGIN markers without an intervening END.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/cafc44f5ee2c6cc8. Report an issue: GitHub.

Appendix: source

Thrown at aeron-cluster/src/main/java/io/aeron/cluster/service/ServiceSnapshotLoader.java:102

            case SnapshotMarkerDecoder.TEMPLATE_ID:
                snapshotMarkerDecoder.wrap(
                    buffer,
                    offset + MessageHeaderDecoder.ENCODED_LENGTH,
                    messageHeaderDecoder.blockLength(),
                    messageHeaderDecoder.version());

                final long typeId = snapshotMarkerDecoder.typeId();
                if (SNAPSHOT_TYPE_ID != typeId)
                {
                    throw new ClusterException("unexpected snapshot type: " + typeId);
                }

                switch (snapshotMarkerDecoder.mark())
                {
                    case BEGIN:
                        if (inSnapshot)
                        {
                            throw new ClusterException("already in snapshot");
                        }
                        inSnapshot = true;
                        appVersion = snapshotMarkerDecoder.appVersion();
                        timeUnit = ClusterClock.map(snapshotMarkerDecoder.timeUnit());
                        return Action.CONTINUE;

                    case END:
                        if (!inSnapshot)
                        {
                            throw new ClusterException("missing begin snapshot");
                        }
                        isDone = true;
                        return Action.BREAK;

                    case SECTION:
                    case NULL_VAL:
                        break;
                }

View on GitHub (pinned to 6d60124e15)