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
- Verify the replay and live destination endpoints are reachable (ports open, correct host)
- Increase the mergeProgressTimeoutMs constructor parameter on slow networks
- Check the archive is actively replaying (logs, recording position advancing)
- 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
- Set mergeProgressTimeoutMs generously for slow/high-latency networks
- Verify UDP endpoints (firewalls, ports) before starting a merge
- Log state and transportCount from the exception to diagnose stalls
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
- failed to fetch remote recording descriptor
- failed to send replay request
- failed get acknowledgement of replay request to: " +…
- failed get replay image for sessionId=" +…
- ReplayMerge Image closed unexpectedly.
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)