aeron-io/aeron · error · ArchiveException
replication image closed unexpectedly
Error message
replication image closed unexpectedly
What it means
Thrown by ReplicationSession.catchup() when, during the catch-up phase of a live replication (a liveDestination is configured and the replay image is still behind srcRecordingPosition), the replay Image becomes closed before it reaches the source recording position. This means the source replay stream terminated prematurely — the replicated recording's catch-up data path died mid-replication instead of completing the merge to the live stream.
Solutions
- Check the source archive logs for the replay session termination reason (recording deleted, archive shutdown, replay cancelled) at the failure position.
- Verify the source recording still exists and its recorded data covers the catch-up range (use ArchiveTool to inspect the catalog) before re-replicating.
- Re-issue the replication from the last successfully replicated position (or use a recording token / truncate destination and restart).
- If loss caused the closure, tune reliability (retransmission settings, term-buffer sizes, aeron.rcv.initial.window.length) so the replay stream survives bursts.
- Ensure no competing replication or ArchiveTool operation stops/deletes the source recording during catch-up.
Example fix
// before: purging old recordings while replication is in flight
archiveControl.purgeRecording(srcRecordingId); // kills the replay mid-catchup
// after: verify the recording is no longer the target of an active replication first
if (!activeReplications.contains(srcRecordingId))
{
archiveControl.purgeRecording(srcRecordingId);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before starting a live replication, confirm the source recording exists and is extendable
RecordingSignal signal;
try (Subscription sub = archive.controlResponsePoller().subscription())
{
// listRecording throws ArchiveException with RECORDING_NOT_FOUND if purged
archive.listRecording(srcRecordingId, (ctrl, lbl, d) -> {});
} Try / catch
try
{
long id = archive.replicate(srcRecordingId, dstRecordingId, liveChannel, liveStreamId,
replicationChannel, replayStreamId);
}
catch (ArchiveException e)
{
if (e.getMessage().contains("replication image closed unexpectedly"))
{
LOGGER.warn("source replay died during catch-up (recording purged or archive restarted); " +
"re-replicating from last position", e);
restartReplicationFromLastAckedPosition();
}
else
{
throw e;
}
} Prevention
- Never purge or truncate a source recording while an active replication targets it — track replication ids vs recording ids
- Supervise the source archive process; restarts mid-catchup close the replay image
- Size term buffers and retransmission settings so replay streams survive transient loss
- Use recording signals/progress polling to detect stalled replication and restart it proactively
When it happens
Trigger: During state CATCHUP, image.position() < srcRecordingPosition and image.isClosed() becomes true; the source archive stopped or failed the replay (source recording deleted, source archive shutdown, replay session cancelled, or flow-control/loss causing the publication to give up), or the destination removed the subscription due to an unblock/no-progress condition.
Common situations: Source recording was purged/truncated (archive catalog cleanup) while being replicated; source archive process restarted mid-catchup; network partition long enough to close the replay connection; source stop-position reached while a live merge was still expected; Aeron driver termination on either side; TTL/linger expiry killing the replay publication.
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
- Archive.Context.replicationChannel must be set
- failed to send replay request
- (int)poller.relevantId()
- srcArchive.pollForErrorResponse()
- failed to send replicate request
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/4aa392ed90c3dd7c.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-archive/src/main/java/io/aeron/archive/ReplicationSession.java:828
workCount += 1;
}
return workCount;
}
private int catchup()
{
int workCount = 0;
if (image.position() >= srcRecordingPosition)
{
state(State.ATTEMPT_LIVE_JOIN, "image position (" + image.position() +
") >= srcRecordingPosition (" + srcRecordingPosition + ")");
workCount += 1;
}
else if (image.isClosed())
{
throw new ArchiveException("replication image closed unexpectedly");
}
return workCount;
}
private int attemptLiveJoin()
{
int workCount = 0;
if (NULL_VALUE == activeCorrelationId)
{
final long correlationId = aeron.nextCorrelationId();
if (srcArchive.archiveProxy().getRecordingPosition(
srcRecordingId, correlationId, srcArchive.controlSessionId()))
{
workCount += trackAction(correlationId);
}
else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))View on GitHub (pinned to 6d60124e15)