aeron-io/aeron · error · TimeoutException

failed get acknowledgement of replay request to: " +…

Error message

failed get acknowledgement of replay request to: " + replicationChannel

What it means

Thrown by ReplicationSession.replay() after the replay request was sent (activeCorrelationId != NULL) but the source archive's control response acknowledging the replay (carrying the replay session id) was never received within actionTimeoutMs. The session polls srcArchive.controlResponsePoller() each doWork() cycle; no matching OK response by the deadline means the source archive never processed or answered the replay request.

Solutions

  1. Inspect the source archive log for the replay request correlationId to see if it was received, rejected, or never arrived.
  2. Verify the control-response channel connectivity in BOTH directions — the source must be able to send responses to the destination archive's response subscription.
  3. Increase the replication action timeout (aeron.archive.replication.timeout) to cover source-archive load spikes.
  4. Check source archive health (CPU, archive catalog/disk I/O, recording log contention) — a stalled archive conductor delays the OK reply.
  5. If the source archive restarted, re-issue the replication; the old correlation id will never be answered.

Example fix

// before: failing because response channel was unreachable
AeronArchive.Configuration.configure(ctx)
    .controlChannel("aeron:udp?endpoint=src-host:8010")
    // controlResponseChannel unreachable from source, default timeout
// after: explicitly set a reachable response channel and longer timeout
AeronArchive.Configuration.configure(ctx)
    .controlChannel("aeron:udp?endpoint=src-host:8010")
    .controlResponseChannel("aeron:udp?endpoint=dst-host:0")
    .backupChannel(null);
systemProps.put("aeron.archive.replication.timeout", "60000000000"); // 60s
Defensive patterns

Strategy: retry

Validate before calling

// Ensure the control-response channel is bound and reachable before replicating
final String responseChannel = archiveCtx.controlResponseChannel();
if (null == responseChannel || responseChannel.contains(":0"))
{
    // wildcard is fine for binding but verify with a round-trip call first
    archive.getRecordingPosition(dstRecordingId); // must return, not time out
}

Try / catch

try
{
    long id = archive.replicate(srcRecordingId, dstRecordingId, replicationChannel, replayStreamId);
    // poll AeronArchive for replication progress instead of fire-and-forget
}
catch (io.aeron.exceptions.TimeoutException e)
{
    LOGGER.error("source archive never ACKed replay request; check source conductor and response channel", e);
    throw e;
}

Prevention

When it happens

Trigger: poller.poll() runs each cycle but hasResponse(poller) never becomes true for the outstanding replay correlationId before timeOfLastActionMs + actionTimeoutMs; the source archive got the request but dropped/failed it, the control response channel is broken, or the response is going to a different control-response channel than the poller subscribes to.

Common situations: Source archive overloaded or stalled (e.g. disk I/O saturation in the archive store) so it cannot service the replay; response publication not connected back to the replicating archive (asymmetric firewall — outbound OK, inbound blocked); source archive restarted and lost the correlation; timeout too aggressive for large/loaded archives.

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

Appendix: source

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

            }
            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))
            {
                throw new TimeoutException("failed to send replay request");
            }
        }
        else
        {
            final ControlResponsePoller poller = srcArchive.controlResponsePoller();
            workCount += poller.poll();

            if (hasResponse(poller))
            {
                srcReplaySessionId = poller.relevantId();
                state(State.AWAIT_IMAGE, "srcReplaySessionId=" + srcReplaySessionId);
            }
            else if (epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))
            {
                throw new TimeoutException("failed get acknowledgement of replay request to: " + replicationChannel);
            }
        }

        return workCount;
    }

    private int awaitImage()
    {
        int workCount = 0;

        final Image image = recordingSubscription.imageBySessionId((int)srcReplaySessionId);
        if (null != image)
        {
            this.image = image;
            state(null == liveDestination ? State.REPLICATE : State.CATCHUP,
                "image.correlationId=" + image.correlationId() +
                ", image.sessionId=" + image.sessionId() +
                ", image.joinPosition=" + image.joinPosition());

View on GitHub (pinned to 6d60124e15)