aeron-io/aeron · error · ConcurrentConcludeException

ConcurrentConcludeException

Error message

ConcurrentConcludeException

What it means

PersistentSubscription.conclude() uses an atomic IS_CONCLUDED flag and throws ConcurrentConcludeException if conclude() is invoked when the subscription has already been (or is concurrently being) concluded. Conclude is a one-time initialization step; a second entry means a lifecycle bug in the calling code.

Solutions

  1. Call conclude() exactly once per PersistentSubscription.Builder instance
  2. Guard the initialization path with application-level synchronization or a once-flag if startup can race
  3. Check for double-invocation from both framework auto-initialization and manual conclude() calls
  4. If a retry restarts initialization, build a fresh Builder instead of reusing the concluded one

Example fix

// before
builder.conclude();
// ...retry path reuses same builder
builder.conclude(); // ConcurrentConcludeException

// after
AtomicBoolean initialized = new AtomicBoolean();
if (initialized.compareAndSet(false, true)) {
    builder.conclude();
}
Defensive patterns

Strategy: try-catch

Validate before calling

AtomicBoolean concluded = new AtomicBoolean();
// pre-check: only conclude if not already concluded
if (!concluded.compareAndSet(false, true)) { skipConclude(); }

Try / catch

try {
    builder.conclude();
} catch (ConcurrentConcludeException e) {
    log.warn("subscription already concluded; ignoring duplicate conclude", e);
}

Prevention

When it happens

Trigger: Calling conclude() twice on the same PersistentSubscription.Builder, or calling it from two threads at once — e.g. an archive replay/connect path racing a manual conclude, or duplicated listener initialization callbacks.

Common situations: Frameworks or wrappers that both auto-conclude the builder and let user code conclude it; retry logic that re-runs an initialization routine containing conclude(); unsynchronized concurrent startup of a persistent subscription.

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

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/client/PersistentSubscription.java:1677

            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();
            }

            if (Aeron.NULL_VALUE == recordingId)
            {
                throw new ConfigurationException("recordingId must be set");
            }

            if (Aeron.NULL_VALUE == liveStreamId)
            {
                throw new ConfigurationException("liveStreamId must be set");
            }

            if (Strings.isEmpty(liveChannel))
            {
                throw new ConfigurationException("liveChannel must be set");
            }

            if (Strings.isEmpty(replayChannel))

View on GitHub (pinned to 6d60124e15)