aeron-io/aeron · warning · TimeoutException

failed to join catchup log

Error message

failed to join catchup log

What it means

TimeoutException (WARN) thrown when the follower fails to reach the point of joining the catchup log at all: before any subscription progress is observed, the leaderHeartbeatTimeoutNs window expires. It is the outer, earlier failure mode of the catchup join compared to "failed to join catchup log as follower".

Solutions

  1. Check whether a valid leader exists at all (cluster may be stuck canvassing); inspect election logs on all members
  2. Fix member channel configuration so replay traffic can flow
  3. Increase leaderHeartbeatTimeoutNs if the environment is legitimately slow
  4. Restart the node to re-enter canvass and retry the election
Defensive patterns

Strategy: retry

Validate before calling

// ensure a quorum of members is up before expecting elections to complete
if (liveMembers() < quorumSize()) {
    LOG.warn("no quorum; catchup joins will time out");
}

Try / catch

catch (TimeoutException e) {
    // outer catchup timeout; verify leader existence then re-enter election
}

Prevention

When it happens

Trigger: followerCatchupInit in an early branch: neither the catchup position send nor a subscription state change happened within timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs().

Common situations: Leader election stalled because candidate leader never responded; total network outage; misconfigured replay channel so no traffic ever flows; watchdog pauses on the follower.

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

Appendix: source

Thrown at aeron-cluster/src/main/java/io/aeron/cluster/Election.java:1083

            verifyLogJoinPosition("followerCatchupAwait", image.joinPosition());
            if (consensusModuleAgent.tryJoinLogAsFollower(image, isLeaderStartup, nowNs))
            {
                state(FOLLOWER_CATCHUP, nowNs, "");
                workCount++;
            }
            else if (ChannelEndpointStatus.ERRORED == logSubscription.channelStatus())
            {
                final String message = "failed to add catchup log as follower - " + logSubscription.channel();
                throw new ClusterException(message, AeronException.Category.WARN);
            }
            else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))
            {
                throw new TimeoutException("failed to join catchup log as follower", AeronException.Category.WARN);
            }
        }
        else if (nowNs >= (timeOfLastStateChangeNs + ctx.leaderHeartbeatTimeoutNs()))
        {
            throw new TimeoutException("failed to join catchup log", AeronException.Category.WARN);
        }

        return workCount;
    }

    private int followerCatchup(final long nowNs)
    {
        int workCount = consensusModuleAgent.catchupPoll(notifiedCommitPosition, nowNs);

        if (null == consensusModuleAgent.liveLogDestination() &&
            consensusModuleAgent.isCatchupNearLive(max(catchupJoinPosition, notifiedCommitPosition)))
        {
            addLiveLogDestination();
            workCount++;
        }

        final long position = ctx.commitPositionCounter().getPlain();
        if (position >= catchupJoinPosition &&

View on GitHub (pinned to 6d60124e15)