{"record":{"id":"c5621cdbb58c673e","repo":"apache/pulsar","slug":"no-tls-context-available-factory-not-initialized","errorCode":null,"errorMessage":"No TLS context available (factory not initialized or closed)","messagePattern":"No TLS context available \\(factory not initialized or closed\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsContextAcquisition.java","lineNumber":140,"sourceCode":"     * {@code FileBasedTlsFactory.Subscription} deferred release), so the re-read always yields a live context.\n     * On the JDK engine {@code retain}/{@code release} are no-ops and this reduces to a plain build.\n     *\n     * @param source a supplier of the current (possibly rotated) factory-owned context borrow\n     * @param build  the build to run against the pinned context (e.g. {@code ctx -> ctx.newHandler(alloc)})\n     * @return the build result\n     * @throws IllegalStateException if no context is available (e.g. the factory was closed)\n     */\n    public static <R> R withPinnedContext(Supplier<SslContext> source, Function<SslContext, R> build) {\n        // Bounded retry: in steady state the first read yields a live context; the loop only re-reads if a\n        // rotation freed the just-read borrow between the read and the pin. The bound prevents an unbounded\n        // spin in the narrow shutdown race where the factory closed and the volatile still points at a freed\n        // context — there the last attempt's IllegalReferenceCountException propagates and the connection fails\n        // cleanly, which is correct during shutdown.\n        IllegalReferenceCountException lastFreed = null;\n        for (int attempt = 0; attempt < 8; attempt++) {\n            SslContext context = source.get();\n            if (context == null) {\n                throw new IllegalStateException(\"No TLS context available (factory not initialized or closed)\");\n            }\n            try {\n                ReferenceCountUtil.retain(context);\n            } catch (IllegalReferenceCountException superseded) {\n                // The borrow was released to refcount 0 (rotated out) between the read and the pin; re-read.\n                lastFreed = superseded;\n                continue;\n            }\n            try {\n                return build.apply(context);\n            } finally {\n                ReferenceCountUtil.release(context);\n            }\n        }\n        throw lastFreed != null ? lastFreed\n                : new IllegalReferenceCountException(\"TLS context repeatedly unavailable while pinning\");\n    }\n","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsContextAcquisition.java#L122-L158","documentation":"TlsContextAcquisition.withPinnedContext borrows the factory's shared SslContext and pins it by refcount for a handshake. If the source returns null — factory not initialized or already closed — there is no context to pin and IllegalStateException is thrown after the bounded re-read loop for rotated-out contexts.","triggerScenarios":"Calling withPinnedContext after PulsarTlsFactory.close() (shutdown/rotation) or before initialization; a handshake racing factory shutdown so source.get() returns null.","commonSituations":"Graceful shutdown closing the TLS factory while in-flight connections still attempt handshakes; a channel referencing a factory from a different lifecycle scope; tests reusing a closed factory.","solutions":["Ensure the factory is initialized before any withPinnedContext call","Do not close the factory while connections still need handshakes; close it last in shutdown ordering","Reopen/recreate the factory if it was closed but the component must keep serving","Fix lifecycle ownership so the factory outlives all transports borrowing contexts"],"exampleFix":"// before\nfactory.close();\nTlsContextAcquisition.withPinnedContext(source, ctx -> handshake(ctx)); // throws\n// after\nTlsContextAcquisition.withPinnedContext(source, ctx -> handshake(ctx));\nfactory.close(); // after all handshakes complete","handlingStrategy":"try-catch","validationCode":"if (!factoryReady.get()) {\n    throw new IllegalStateException(\"Refusing handshake: TLS factory not initialized\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    TlsContextAcquisition.withPinnedContext(source, ctx -> handshake(ctx));\n} catch (IllegalStateException e) {\n    if (shuttingDown.get()) { log.debug(\"Handshake skipped during factory shutdown\"); return; }\n    throw e;\n}","preventionTips":["Close the TLS factory last in shutdown ordering, after transports drain","Track initialization state and refuse handshakes before init completes","Avoid sharing one factory across lifecycle scopes"],"tags":["tls","lifecycle","race-condition","netty"],"backgroundTag":"tls-factory-not-initialized","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}