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
- Call conclude() exactly once per PersistentSubscription.Builder instance
- Guard the initialization path with application-level synchronization or a once-flag if startup can race
- Check for double-invocation from both framework auto-initialization and manual conclude() calls
- 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
- Conclude exactly once per Builder; centralize subscription construction
- Synchronize multi-threaded startup or use compareAndSet once-flags
- Do not reuse a concluded Builder across retries — build a fresh one
- Review framework hooks that may auto-conclude on your behalf
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
- unexpected Aeron close
- unexpected driver close
- response publication is closed: " + session
- ConcurrentConcludeException
- Concurrent conclude of ConsensusModule.Context not permitted
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)