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
- 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
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
- 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
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Client TLS factory <factory.getClass().getName()> supplied n
- the PulsarTlsFactory passed to tlsFactory(...) has already b
- ManagedLedgerFactory is already closed.
- ManagedLedger ${name} has already been closed
- Cannot start the service once it was stopped
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/c5621cdbb58c673e.
Report an issue: GitHub.