aeron-io/aeron · warning · TimeoutException

failed to join catchup log as follower

Error message

failed to join catchup log as follower

What it means

TimeoutException (WARN) thrown when the follower subscribed to the catchup replay log but never actually joins the catchup image/log within ctx.leaderHeartbeatTimeoutNs. The subscription was added successfully but no connection or progress on it occurred before the deadline, so the catchup phase fails.

Solutions

  1. Confirm the leader is alive and its replay service is running; check leader logs for replay errors
  2. Increase ctx.leaderHeartbeatTimeoutNs() to allow slow replay connects
  3. Check network path (ports/firewall) between follower and leader for the replay stream
  4. Restart the member to trigger a fresh election if the leader has since changed

Example fix

// before
ctx.leaderHeartbeatTimeoutNs(TimeUnit.SECONDS.toNanos(10));
// after
ctx.leaderHeartbeatTimeoutNs(TimeUnit.SECONDS.toNanos(60));
Defensive patterns

Strategy: retry

Try / catch

catch (TimeoutException e) {
    // catchup join timed out; node should re-canvas; restart member if stuck
    memberRestartIfRepeated(e, 3);
}

Prevention

When it happens

Trigger: In FOLLOWER_CATCHUP_AWAIT with the subscription not errored: if the image never connects (or state never advances to FOLLOWER_CATCHUP) before timeOfLastStateChangeNs + leaderHeartbeatTimeoutNs, this is thrown.

Common situations: Leader not replaying (overloaded, dead, or unreachable); network drop after subscription add; leaderHeartbeatTimeoutNs too short for big backlogs; leader's replay service busy.

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

Appendix: source

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

        int workCount = 0;

        final Image image = logSubscription.imageBySessionId(logSessionId);
        if (null != image)
        {
            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();

View on GitHub (pinned to 6d60124e15)