aeron-io/aeron · error · TimeoutException

ReplayMerge no progress: state=

Error message

ReplayMerge no progress: state=<state>, activeTransportCount=<transportCount>

What it means

ReplayMerge tracks progress; if no position or state progress is made within mergeProgressTimeoutMs, checkProgress throws a TimeoutException naming the merge state and active transport count. This prevents a wedged merge (e.g. lost replay, unreachable live destination) from hanging forever.

Solutions

  1. Verify the replay and live destination endpoints are reachable (ports open, correct host)
  2. Increase the mergeProgressTimeoutMs constructor parameter on slow networks
  3. Check the archive is actively replaying (logs, recording position advancing)
  4. Restart the ReplayMerge after diagnosing the stall

Example fix

// before
new ReplayMerge(archive, sub, replayChannel, replayDest, liveDest, recordingId, 0, 5_000, ctx.epochClock(), 1024, "");
// after: generous timeout
new ReplayMerge(archive, sub, replayChannel, replayDest, liveDest, recordingId, 0, 60_000, ctx.epochClock(), 1024, "");
Defensive patterns

Strategy: retry

Validate before calling

// pre-check connectivity to replay/live destinations before merging
if (!isEndpointReachable(replayDest) || !isEndpointReachable(liveDest)) { awaitNetwork(); }

Try / catch

catch (TimeoutException e) { if (e.getMessage().startsWith("ReplayMerge no progress")) { retryWithBackoff(() -> newReplayMerge()); } }

Prevention

When it happens

Trigger: nowMs exceeds timeOfLastProgressMs + mergeProgressTimeoutMs with no position advance — stalled replay delivery, unreachable live destination, or archive not sending data.

Common situations: Firewall blocking the replay or live UDP destinations; archive overloaded; mergeProgressTimeoutMs set too small for slow catchup on high-latency links.

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

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/client/ReplayMerge.java:581

    private boolean shouldAddLiveDestination(final long position)
    {
        return !isLiveAdded &&
            (nextTargetPosition - position) <= Math.min(image.termBufferLength() >> 2, LIVE_ADD_MAX_WINDOW);
    }

    private boolean shouldStopAndRemoveReplay(final long position)
    {
        return isLiveAdded &&
            (nextTargetPosition - position) <= REPLAY_REMOVE_THRESHOLD &&
            image.activeTransportCount() >= 2;
    }

    private void checkProgress(final long nowMs)
    {
        if (nowMs > (timeOfLastProgressMs + mergeProgressTimeoutMs))
        {
            final int transportCount = null != image ? image.activeTransportCount() : 0;
            throw new TimeoutException(
                "ReplayMerge no progress: state=" + state + ", activeTransportCount=" + transportCount);
        }

        if (NULL_VALUE == activeCorrelationId &&
            (nowMs > (timeOfLastScheduledArchivePollMs + ARCHIVE_POLL_INTERVAL_MS)))
        {
            timeOfLastScheduledArchivePollMs = nowMs;
            pollForResponse(archive, NULL_VALUE);
        }
    }

    private static boolean pollForResponse(final AeronArchive archive, final long correlationId)
    {
        final ControlResponsePoller poller = archive.controlResponsePoller();

        final int pollCount = poller.poll();
        if (poller.isPollComplete())
        {

View on GitHub (pinned to 6d60124e15)