aeron-io/aeron · error · ArchiveException

(int)poller.relevantId()

(int)poller.relevantId()

Error message

poller.errorMessage()

What it means

When a control response from the source archive arrives with code ERROR, ReplicationSession.hasResponse() rethrows it as an ArchiveException carrying the source archive's errorMessage and errorCode (taken from poller.relevantId()). This propagates any error the SOURCE archive produced while serving the replication (e.g. failed replay or recording-position request) to the destination side, failing the replication session.

Solutions

  1. Read the embedded errorCode/errorMessage — it names the exact source-archive failure (e.g. UNKNOWN_RECORDING) and fix accordingly.
  2. Confirm srcRecordingId exists on the source archive (listRecordings) before replicating.
  3. Ensure both archives run compatible Aeron versions (control protocol must match).
  4. Check source archive logs for the error reported against this control session/correlationId.
  5. Retry the replication once the underlying source-side condition (deleted recording, resource exhaustion) is resolved.

Example fix

// before
final long replicationId = dstArchive.replication(srcRecordingId, channel);
// after
try {
    final long replicationId = dstArchive.replication(srcRecordingId, channel);
} catch (ArchiverException e) {
    if (e.errorCode() == ArchiveException.UNKNOWN_RECORDING) {
        log.error("source recording " + srcRecordingId + " missing on source archive; skip replication");
    } else {
        throw e;
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the source recording exists before replicating to avoid the common UNKNOWN_RECORDING source error:
long count = countRecordings(sourceArchive, srcRecordingId);
if (count == 0) throw new IllegalArgumentException("recordingId " + srcRecordingId + " not found on source archive");

Try / catch

try {
    replicationId = archive.replication(srcRecordingId, channel);
} catch (ArchiverException e) {
    // errorCode/errorMessage are propagated verbatim from the source archive
    log.error("source archive rejected replication: code=" + e.errorCode() + " msg=" + e.getMessage());
    handleSourceArchiveError(e.errorCode());
}

Prevention

When it happens

Trigger: Any request ReplicationSession sends to the source archive (start replay via extendRecording/replay, getRecordingPosition, stopReplay) is rejected by the source archive; the poller observes an ERROR control response for the source control session and line 909 rethrows it.

Common situations: Requesting a replay of a recordingId that doesn't exist on the source; source archive rejecting an operation due to internal error or exhausted resources; version mismatch between the two Aeron archives; source recording deleted mid-replication.

Related errors


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

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/ReplicationSession.java:909

                {
                    throw new TimeoutException("failed to get recording position");
                }

                trackAction(NULL_VALUE);
            }
        }

        return workCount;
    }

    private boolean hasResponse(final ControlResponsePoller poller)
    {
        if (poller.isPollComplete() && poller.controlSessionId() == srcArchive.controlSessionId())
        {
            final ControlResponseCode code = poller.code();
            if (ControlResponseCode.ERROR == code)
            {
                throw new ArchiveException(poller.errorMessage(), (int)poller.relevantId());
            }

            return poller.correlationId() == activeCorrelationId && ControlResponseCode.OK == code;
        }

        return false;
    }

    private void error(final String msg, final int errorCode)
    {
        controlSession.sendErrorResponse(replicationId, errorCode, msg);
    }

    private void signal(final long position, final RecordingSignal recordingSignal)
    {
        final long subscriptionId = null != recordingSubscription ? recordingSubscription.registrationId() : NULL_VALUE;
        controlSession.sendSignal(replicationId, dstRecordingId, subscriptionId, position, recordingSignal);
    }

View on GitHub (pinned to 6d60124e15)