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

  1. Call conclude() exactly once per ConsensusModule.Context instance
  2. If a second component needs the configuration, use ctx.clone() and conclude the copy
  3. 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

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


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)