{"record":{"id":"bd3097cc09f59fab","repo":"aeron-io/aeron","slug":"client-is-closed","errorCode":null,"errorMessage":"client is closed","messagePattern":"client is closed","errorType":"exception","errorClass":"ArchiveException","httpStatus":null,"severity":"error","filePath":"aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java","lineNumber":2594,"sourceCode":"    private void dispatchRecordingSignal(final ControlResponsePoller poller)\n    {\n        context.recordingSignalConsumer().onSignal(\n            poller.controlSessionId(),\n            poller.correlationId(),\n            poller.recordingId(),\n            poller.subscriptionId(),\n            poller.position(),\n            poller.recordingSignal());\n    }\n\n    private void ensureConnected()\n    {\n        final State currentState = state;\n        if (State.CONNECTED != currentState)\n        {\n            if (State.CLOSED == currentState)\n            {\n                throw new ArchiveException(\"client is closed\");\n            }\n            else\n            {\n                close();\n            }\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 void state(final State newState)\n    {","sourceCodeStart":2576,"sourceCodeEnd":2612,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java#L2576-L2612","documentation":"AeronArchive tracks its connection lifecycle with a State enum. This guard is in the code path that verifies the client is CONNECTED before performing work; if the state is CLOSED, further use is impossible, so the library throws this ArchiveException instead of proceeding on a dead connection.","triggerScenarios":"Calling any AeronArchive method (e.g. startRecording, stopRecording, pollForNextResponse) after close() was called on the archive client, or a timeout/error path closed the connection internally.","commonSituations":"Application shutdown ordering where the archive client is closed while worker threads still use it; catching a connect timeout elsewhere and continuing; reusing an archive client across requests after an error handler closed it.","solutions":["Check AeronArchive.state() / isClosed() before use, or hold a single reference and stop using it after close().","Create a new AeronArchive instance (AeronArchive.connect(ctx)) to replace the closed one.","Reorder shutdown so all archive work completes before close(), and make worker threads observe a shutdown flag.","Catch ArchiveException in the worker loop and treat it as a shutdown signal."],"exampleFix":"// before\narchive.startRecording(channel, streamId, SourceLocation.LOCAL); // may be closed\n// after\nif (archive.isClosed())\n{\n    archive = AeronArchive.connect(archiveCtx);\n}\narchive.startRecording(channel, streamId, SourceLocation.LOCAL);","handlingStrategy":"validation","validationCode":"if (archive == null || archive.isClosed()) { archive = AeronArchive.connect(archiveCtx.clone()); }","typeGuard":"boolean usable(AeronArchive a) { return a != null && !a.isClosed(); }","tryCatchPattern":null,"preventionTips":["Check isClosed() before each archive operation in long-lived workers.","Centralize archive access in one owner component that recreates the client on demand.","Order shutdown: finish archive work, then close the client, then close Aeron.","Never cache AeronArchive across application restarts or error-handler closes."],"tags":["aeron","archive","closed-resource","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-15T23:17:13.987Z"}