{"record":{"id":"c3b52e678e074f1f","repo":"aeron-io/aeron","slug":"reentrant-calls-not-permitted-during-callbacks-c3b52e","errorCode":null,"errorMessage":"reentrant calls not permitted during callbacks","messagePattern":"reentrant calls not permitted during callbacks","errorType":"exception","errorClass":"AeronException","httpStatus":null,"severity":"error","filePath":"aeron-client/src/main/java/io/aeron/ClientConductor.java","lineNumber":1728,"sourceCode":"\n    private void ensureActive()\n    {\n        if (isClosed)\n        {\n            throw new AeronException(\"Aeron client is closed\");\n        }\n\n        if (isTerminating)\n        {\n            throw new AeronException(\"Aeron client is terminating\");\n        }\n    }\n\n    private void ensureNotReentrant()\n    {\n        if (isInCallback)\n        {\n            throw new AeronException(\"reentrant calls not permitted during callbacks\");\n        }\n    }\n\n    private LogBuffers logBuffers(final long registrationId, final String logFileName, final String channel)\n    {\n        LogBuffers logBuffers = logBuffersByIdMap.get(registrationId);\n        if (null == logBuffers)\n        {\n            try\n            {\n                logBuffers = logBuffersFactory.map(logFileName);\n\n                if (ctx.preTouchMappedMemory())\n                {\n                    logBuffers.preTouch();\n                }\n\n                logBuffersByIdMap.put(registrationId, logBuffers);","sourceCodeStart":1710,"sourceCodeEnd":1746,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-client/src/main/java/io/aeron/ClientConductor.java#L1710-L1746","documentation":"ensureNotReentrant() throws this when a client callback (e.g. AvailableImageHandler, UnavailableCounterHandler, ReservedValueSupplier) makes a re-entrant call back into the ClientConductor that would mutate state while the conductor is iterating inside its own callback. Re-entrant mutation during callbacks can corrupt conductor state, so it is forbidden.","triggerScenarios":"From within an onAvailableImage/onUnavailableImage/onAvailableCounter callback, calling APIs that require conductor state changes such as Aeron.close(), addSubscription/addPublication removal, or other state-mutating calls on the same client thread.","commonSituations":"Calling aeron.close() inside an unavailable-image handler; adding/removing subscriptions inside image callbacks; complex teardown logic embedded in listeners running on the conductor thread.","solutions":["Do not call state-changing client APIs from inside callbacks; instead enqueue the action and execute it on another thread","Use Aeron's async APIs: schedule close/add work outside the callback via an executor","Use AtomicCounter or queue signals from the callback and act in your own loop","If teardown is needed, defer with a separate executor thread that performs the close"],"exampleFix":"// before\nsubscription.onUnavailableImage(img -> aeron.close()); // reentrant call\n// after\nsubscription.onUnavailableImage(img -> closeExecutor.execute(() -> aeron.close()));","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { conductorCall(); } catch (AeronException e) { if (e.getMessage().contains(\"reentrant\")) { deferToOtherThread(action); } else { throw e; } }","preventionTips":["Never call close/add/remove APIs from inside available/unavailable handlers","Use an executor to defer teardown work out of callbacks","Keep handlers lightweight: enqueue, don't mutate conductor state"],"tags":["aeron","reentrancy","callbacks","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}