apache/pulsar · error · IllegalStateException

No TLS context available (factory not initialized or closed)

Error message

No TLS context available (factory not initialized or closed)

What it means

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.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsContextAcquisition.java:140

     * {@code FileBasedTlsFactory.Subscription} deferred release), so the re-read always yields a live context.
     * On the JDK engine {@code retain}/{@code release} are no-ops and this reduces to a plain build.
     *
     * @param source a supplier of the current (possibly rotated) factory-owned context borrow
     * @param build  the build to run against the pinned context (e.g. {@code ctx -> ctx.newHandler(alloc)})
     * @return the build result
     * @throws IllegalStateException if no context is available (e.g. the factory was closed)
     */
    public static <R> R withPinnedContext(Supplier<SslContext> source, Function<SslContext, R> build) {
        // Bounded retry: in steady state the first read yields a live context; the loop only re-reads if a
        // rotation freed the just-read borrow between the read and the pin. The bound prevents an unbounded
        // spin in the narrow shutdown race where the factory closed and the volatile still points at a freed
        // context — there the last attempt's IllegalReferenceCountException propagates and the connection fails
        // cleanly, which is correct during shutdown.
        IllegalReferenceCountException lastFreed = null;
        for (int attempt = 0; attempt < 8; attempt++) {
            SslContext context = source.get();
            if (context == null) {
                throw new IllegalStateException("No TLS context available (factory not initialized or closed)");
            }
            try {
                ReferenceCountUtil.retain(context);
            } catch (IllegalReferenceCountException superseded) {
                // The borrow was released to refcount 0 (rotated out) between the read and the pin; re-read.
                lastFreed = superseded;
                continue;
            }
            try {
                return build.apply(context);
            } finally {
                ReferenceCountUtil.release(context);
            }
        }
        throw lastFreed != null ? lastFreed
                : new IllegalReferenceCountException("TLS context repeatedly unavailable while pinning");
    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Ensure the factory is initialized before any withPinnedContext call
  2. Do not close the factory while connections still need handshakes; close it last in shutdown ordering
  3. Reopen/recreate the factory if it was closed but the component must keep serving
  4. Fix lifecycle ownership so the factory outlives all transports borrowing contexts

Example fix

// before
factory.close();
TlsContextAcquisition.withPinnedContext(source, ctx -> handshake(ctx)); // throws
// after
TlsContextAcquisition.withPinnedContext(source, ctx -> handshake(ctx));
factory.close(); // after all handshakes complete
Defensive patterns

Strategy: try-catch

Validate before calling

if (!factoryReady.get()) {
    throw new IllegalStateException("Refusing handshake: TLS factory not initialized");
}

Try / catch

try {
    TlsContextAcquisition.withPinnedContext(source, ctx -> handshake(ctx));
} catch (IllegalStateException e) {
    if (shuttingDown.get()) { log.debug("Handshake skipped during factory shutdown"); return; }
    throw e;
}

Prevention

When it happens

Trigger: Calling withPinnedContext after PulsarTlsFactory.close() (shutdown/rotation) or before initialization; a handshake racing factory shutdown so source.get() returns null.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/c5621cdbb58c673e. Report an issue: GitHub.