aeron-io/aeron · error · TimeoutException

failed to get recording position

Error message

failed to get recording position

What it means

Thrown by ReplicationSession.srcRecordingPosition when the getRecordingPosition request was sent but the control response poller receives no relevant response from the source archive within actionTimeoutMs. Aeron throws TimeoutException to stop the replication rather than wait indefinitely on a stalled control channel. Like the other replication timeouts, it indicates the source archive's control response path is not delivering answers.

Solutions

  1. Verify the source archive is responsive (query it manually with AeronArchive.listRecordings from another client).
  2. Increase the replication action timeout in the Archive client context.
  3. Check the control response channel for loss/misconfiguration; use a reliable path for archive control traffic.
  4. Retry the replication; repeated failure at this state points to a source-archive-side problem to diagnose with logs.
  5. Keep Aeron up to date; control-response polling robustness has improved over releases.

Example fix

// before
ctx.actionTimeoutNs(TimeUnit.SECONDS.toNanos(5));
// after: allow for slow control responses on WAN replication
ctx.actionTimeoutNs(TimeUnit.SECONDS.toNanos(60));
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check the source archive answers control queries before replicating
try (AeronArchive probe = AeronArchive.connect(clientCtx)) {
    probe.listRecordingsForUri(0, 1, recordingUriFragment, (cid, descriptors) -> {});
}

Try / catch

try {
    session.doWork();
} catch (TimeoutException e) {
    if (e.getMessage().contains("failed to get recording position")) {
        log.warn("no control response from source archive; retrying replication");
        restartReplicationWithBackoff();
    }
}

Prevention

When it happens

Trigger: Replication session waiting on the source archive's ControlResponsePoller; poller.poll() yields no response matching the outstanding correlation id before epochClock.time() exceeds timeOfLastActionMs + actionTimeoutMs — source archive conductor overloaded, response fragments lost, or timeout too small.

Common situations: Slow/WAN links between archives with default timeouts; source archive under heavy load delaying control responses; response-channel misconfiguration or packet loss; source archive restart that orphaned the outstanding correlation id.

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/d9729cf5f38dd7a1. Report an issue: GitHub.

Appendix: source

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

        }
        else
        {
            final ControlResponsePoller poller = srcArchive.controlResponsePoller();
            workCount += poller.poll();

            if (hasResponse(poller))
            {
                srcRecordingPosition = poller.relevantId();
                if (NULL_POSITION == srcRecordingPosition && null != liveDestination)
                {
                    throw new ArchiveException("cannot live merge without active source recording");
                }

                state(State.EXTEND, "");
            }
            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))
            {
                throw new TimeoutException("failed to get recording position");
            }
        }

        return workCount;
    }

    private int extend()
    {
        final boolean isMds = isTagged || null != liveDestination;
        final ChannelUri channelUri = ChannelUri.parse(replicationChannel);
        final String endpoint = channelUri.get(CommonContext.ENDPOINT_PARAM_NAME);
        channelUri.put(CommonContext.REJOIN_PARAM_NAME, "false");
        if (!channelUri.hasControlModeResponse())
        {
            channelUri.put(CommonContext.SESSION_ID_PARAM_NAME, Integer.toString(replaySessionId));
        }

        if (isMds)

View on GitHub (pinned to 6d60124e15)