aeron-io/aeron · error · ConcurrentConcludeException
Concurrent conclude of ConsensusModule.Context not permitted
Error message
Concurrent conclude of ConsensusModule.Context not permitted
What it means
ConsensusModule.Context.conclude() guards itself with an atomic IS_CONCLUDED flag; if conclude() (or a caller like Context.conclude() invoked indirectly via the media driver/consensus module startup) is re-entered while already concluded, it throws ConcurrentConcludeException. conclude() is not idempotent because it mutates state (creates files, connects Aeron), so a second concurrent call would corrupt configuration. This is an internal-invariant guard for lifecycle misuse.
Solutions
- Call conclude() exactly once per ConsensusModule.Context instance
- If a second component needs the configuration, use ctx.clone() and conclude the copy
- Do not call conclude() manually when passing the context to ConsensusModule/ClusteredServiceContainer — they conclude it themselves
Example fix
// before ConsensusModule.Context ctx = new ConsensusModule.Context(); ctx.conclude(); new ConsensusModule(ctx).start(); // concludes again -> ConcurrentConcludeException // after ConsensusModule.Context ctx = new ConsensusModule.Context(); new ConsensusModule(ctx).start(); // let the module conclude once
Defensive patterns
Strategy: validation
Validate before calling
if (ctx == null) throw new IllegalStateException("context required");
// conclude exactly once; if re-conclusion is possible, keep a boolean:
if (!concluded) { concluded = true; ctx.conclude(); } Try / catch
try { ctx.conclude(); } catch (ConcurrentConcludeException e) { /* context already concluded; reuse it or bail out */ } Prevention
- Never call conclude() manually on a context you hand to ConsensusModule/ClusteredServiceContainer
- Use Context.clone() for additional concluded copies
- Keep one Context instance per cluster node lifecycle
When it happens
Trigger: Calling ctx.conclude() twice, from two threads, or calling code that internally concludes the same Context (e.g. ConsensusModule launching with a context the user already concluded).
Common situations: Sharing one ConsensusModule.Context across multiple clustered services or containers; wrapping conclude() in retry logic; double startup after a failed bootstrap where the context object is reused.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ConcurrentConcludeException
- ConcurrentConcludeException
- catalogFileSyncLevel
- invalid fileIoMaxLength=
- Archive.Context.controlChannel must be set
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/b973cfb9e3be730f.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModule.java:1729
try
{
return (Context)super.clone();
}
catch (final CloneNotSupportedException ex)
{
throw new RuntimeException(ex);
}
}
/**
* Conclude configuration by setting up defaults when specifics are not provided.
*/
@SuppressWarnings("MethodLength")
public void conclude()
{
if ((boolean)IS_CONCLUDED_VH.getAndSet(this, true))
{
throw new ConcurrentConcludeException();
}
validateLogChannel();
if (serviceCount < 0 || serviceCount > MAX_SERVICE_COUNT)
{
throw new ClusterException("service count of range [0, " + MAX_SERVICE_COUNT + "]: " + serviceCount);
}
if (null == clusterDir)
{
clusterDir = new File(clusterDirectoryName);
}
if (null == markFileDir)
{
final String dir = ClusteredServiceContainer.Configuration.markFileDir();
markFileDir = Strings.isEmpty(dir) ? clusterDir : new File(dir);View on GitHub (pinned to 6d60124e15)