aeron-io/aeron · error · TimeoutException

failed to fetch remote recording descriptor

Error message

failed to fetch remote recording descriptor

What it means

Thrown by ReplicationSession.replicateDescriptor when, after successfully sending a listRecording request to the source archive, the recording descriptor poller receives zero fragments for longer than actionTimeoutMs, so the remote recording descriptor never arrives. Aeron throws TimeoutException to abort a replication whose source archive is not answering descriptor queries. It is a driver-side timeout of an in-flight archive control request, not a lookup miss (a missing recording is reported separately as 'unknown src recording id').

Solutions

  1. Verify the source archive is reachable and its conductor is alive (check source archive logs for the listRecording request).
  2. Increase the replication/connect timeout in the Archive client context (actionTimeoutMs / archive context timeouts) to cover the actual network RTT.
  3. Ensure the control request/response channel configuration matches on source and destination and the network path is reliable (consider reliable/TCP control streams or fix packet loss).
  4. Retry the replication; if it always times out at the same step, capture both archives' logs and check descriptor poller activity.
  5. Upgrade Aeron — replication session timeout handling has been tuned across releases.

Example fix

// before
final ArchiveClientContext ctx = new ArchiveClientContext()
    .aeron(aeron);
// after: give the replication session a longer control timeout on slow links
final ArchiveClientContext ctx = new ArchiveClientContext()
    .aeron(aeron)
    .connectTimeoutNs(TimeUnit.SECONDS.toNanos(30));
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: confirm the source recording exists and the archive responds
try (AeronArchive probe = AeronArchive.connect(ctx)) {
    if (!probe.listRecording(srcRecordingId, (c, i) -> {})) {
        throw new IllegalStateException("src recording " + srcRecordingId + " not found on source archive");
    }
}

Type guard

boolean sourceArchiveReachable(AeronArchive src) {
    return src != null && src.controlResponsePoller().subscription().isConnected();
}

Try / catch

try {
    replicationSession.doWork();
} catch (TimeoutException e) {
    if (e.getMessage().contains("failed to fetch remote recording descriptor")) {
        log.warn("source archive did not answer descriptor query; retrying with longer timeout");
        scheduleRetryWithBackoff();
    }
}

Prevention

When it happens

Trigger: Calling AeronArchive.startReplication/replicate (or replicate with live merge) where the source archive accepts the listRecording subscription offer but never publishes the descriptor: source archive conductor stalled or dead, control-channel loss (UDP drops on an unreliable control stream), source recording list backing up, or actionTimeoutMs (aeron.archive.replication.connect.timeout style context) too small for a loaded archive.

Common situations: Replicating across WAN links with default timeouts; source archive paused by long GC or swapped out; control request/response channels misconfigured (different control endpoints than the replication channel) so responses never return; heavy archive load delaying the conductor; network partitions between source and destination clusters.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

            {
                throw new TimeoutException("failed to list remote recording descriptor");
            }
        }
        else
        {
            final RecordingDescriptorPoller poller = srcArchive.recordingDescriptorPoller();
            final int fragments = poller.poll();

            if (poller.isDispatchComplete() && poller.remainingRecordCount() > 0)
            {
                final String errorMsg = "unknown src recording id " + srcRecordingId;
                state(State.DONE, errorMsg);
                error(errorMsg, ArchiveException.UNKNOWN_RECORDING);
            }

            if (0 == fragments && epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))
            {
                throw new TimeoutException("failed to fetch remote recording descriptor");
            }

            workCount += fragments;
        }

        return workCount;
    }

    private int srcRecordingPosition()
    {
        int workCount = 0;

        if (NULL_VALUE == activeCorrelationId)
        {
            final long correlationId = aeron.nextCorrelationId();
            final long controlSessionId = srcArchive.controlSessionId();
            if (srcArchive.archiveProxy().getRecordingPosition(srcRecordingId, correlationId, controlSessionId))
            {

View on GitHub (pinned to 6d60124e15)