aeron-io/aeron · error · ClusterException
startupCanvassTimeoutNs=
Error message
startupCanvassTimeoutNs=<value> must be a multiple of leaderHeartbeatTimeoutNs=<value>
What it means
conclude() validates that startupCanvassTimeoutNs is at least twice leaderHeartbeatTimeoutNs (the check startupCanvassTimeoutNs / leaderHeartbeatTimeoutNs < 2). The canvass window must span multiple heartbeat intervals so election messaging can complete; a too-short canvass timeout makes leader election unreliable and is rejected.
Solutions
- Increase startupCanvassTimeoutNs to at least 2x (typically 5s vs 10s heartbeat: e.g. canvass 10s, heartbeat 5s)
- Or decrease leaderHeartbeatTimeoutNs so the ratio is >= 2
- Verify units — both values are nanoseconds
Example fix
// before ctx.leaderHeartbeatTimeoutNs(5_000_000_000L) .startupCanvassTimeoutNs(4_000_000_000L); // < 2x // after ctx.leaderHeartbeatTimeoutNs(5_000_000_000L) .startupCanvassTimeoutNs(10_000_000_000L); // 2x heartbeat
Defensive patterns
Strategy: validation
Validate before calling
if (startupCanvassTimeoutNs / leaderHeartbeatTimeoutNs < 2) {
throw new IllegalArgumentException("startupCanvassTimeoutNs must be >= 2x leaderHeartbeatTimeoutNs");
} Try / catch
try { ctx.conclude(); } catch (ClusterException e) { if (e.getMessage().contains("startupCanvassTimeoutNs")) { /* fix timeout ratio in config */ } } Prevention
- Keep defaults unless you understand election timing
- Assert the 2x ratio in config validation code
- Use consistent nanosecond units for both settings
When it happens
Trigger: Setting Context.startupCanvassTimeoutNs() to a value less than 2x leaderHeartbeatTimeoutNs() before conclude().
Common situations: Tuning election timeouts for fast failover and shrinking canvass below the heartbeat multiple; copying partial config from another cluster; typo putting nanosecond vs millisecond values in the wrong field.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- interServiceTimeoutNs=
- timeout awaiting commit position
- failed to send catchup position
- failed to join catchup log as follower
- failed to join catchup log
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/996bec768fd15ab3.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java:1785
throw new UncheckedIOException(ex);
}
if (null == clusterMembers)
{
throw new ClusterException("ConsensusModule.Context.clusterMembers must be set");
}
if (deleteDirOnStart)
{
IoUtil.delete(clusterDir, false);
}
IoUtil.ensureDirectoryExists(clusterDir, "cluster");
IoUtil.ensureDirectoryExists(markFileDir, "mark file");
if (startupCanvassTimeoutNs / leaderHeartbeatTimeoutNs < 2)
{
throw new ClusterException(
"startupCanvassTimeoutNs=" + startupCanvassTimeoutNs +
" must be a multiple of leaderHeartbeatTimeoutNs=" + leaderHeartbeatTimeoutNs);
}
if (null == clusterClock)
{
final String clockClassName = System.getProperty(
CLUSTER_CLOCK_PROP_NAME, MillisecondClusterClock.class.getName());
try
{
clusterClock = (ClusterClock)Class.forName(clockClassName).getConstructor().newInstance();
}
catch (final Exception e)
{
throw new ClusterException("failed to instantiate ClusterClock " + clockClassName, e);
}
}
View on GitHub (pinned to 6d60124e15)