{"record":{"id":"8fa688eaf0950b7e","repo":"apache/pulsar","slug":"pulsarhttpclientfactory-for-clientinstanceid-is","errorCode":null,"errorMessage":"PulsarHttpClientFactory for ${clientInstanceId} is closed","messagePattern":"PulsarHttpClientFactory for (.+?) is closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/v5/FrameworkHttpClientFactory.java","lineNumber":129,"sourceCode":"     * @param clientInstanceId a stable id of the owning client, for logging\n     */\n    public FrameworkHttpClientFactory(Supplier<EventLoopGroup> eventLoopGroup, Supplier<Timer> timer,\n            Supplier<NameResolver<InetAddress>> nameResolver, Supplier<PulsarTlsFactory> tlsFactory,\n            ClientConfigurationData conf, String clientInstanceId) {\n        this.eventLoopGroup = eventLoopGroup;\n        this.timer = timer;\n        this.nameResolver = nameResolver;\n        this.tlsFactory = tlsFactory;\n        this.conf = conf;\n        this.clientInstanceId = clientInstanceId;\n    }\n\n\n    @Override\n    public PulsarHttpClient newHttpClient(PulsarHttpClientConfig config) {\n        synchronized (lock) {\n            if (closed) {\n                throw new IllegalStateException(\"PulsarHttpClientFactory for \" + clientInstanceId + \" is closed\");\n            }\n            DefaultAsyncHttpClientConfig.Builder builder = baseBuilder(config);\n            TlsHandle<SslContext> tlsSubscription = configureTls(builder, config);\n            builder.setEventLoopGroup(eventLoopGroup.get());\n            Timer sharedTimer = timer.get();\n            if (sharedTimer != null) {\n                builder.setNettyTimer(sharedTimer);\n            }\n            configureSocks5(builder);\n            AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(builder.build());\n            // The self-deregistering runnable needs the client instance, which does not exist yet; capture it\n            // through a holder so close() removes exactly this instance from the tracking set.\n            FrameworkHttpClient[] ref = new FrameworkHttpClient[1];\n            FrameworkHttpClient client = new FrameworkHttpClient(asyncHttpClient, config, resolveNameResolver(),\n                    tlsSubscription, () -> deregister(ref[0]));\n            ref[0] = client;\n            openClients.add(client);\n            return client;","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/v5/FrameworkHttpClientFactory.java#L111-L147","documentation":"FrameworkHttpClientFactory creates PulsarHttpClient instances sharing one event loop group and timer. Once close() has been called, the factory is marked closed and newHttpClient() refuses to hand out new clients holding onto the (now shutting down) shared resources, throwing this IllegalStateException under the factory lock.","triggerScenarios":"Calling factory.newHttpClient(config) after factory.close() has run. Seen in production via the client bootstrap path and in tests such as testFactoryCloseClosesInstancesAndRejectsNew.","commonSituations":"Creating a new PulsarClient with a factory that was already shut down during application restart logic; reusing a singleton factory after a failed client initialization closed it; race between a shutdown hook and request-handling code creating clients.","solutions":["Create a new FrameworkHttpClientFactory instance instead of reusing the closed one.","Track factory lifecycle: guard newHttpClient calls with a closed check or make the factory a scoped resource tied to the client's lifetime.","If the factory was closed unexpectedly, find the close() caller (e.g. a previous client's failure path) and fix the lifecycle ownership."],"exampleFix":"// before\nfactory.close();\n// ... later\nPulsarHttpClient c = factory.newHttpClient(config); // throws\n// after\nfactory.close();\nfactory = new FrameworkHttpClientFactory(clientInstanceId);\nPulsarHttpClient c = factory.newHttpClient(config);","handlingStrategy":"try-catch","validationCode":"boolean usable = false;\ntry {\n    java.lang.reflect.Field f = factory.getClass().getDeclaredField(\"closed\");\n    f.setAccessible(true); // or track closed state yourself\n    usable = !f.getBoolean(factory);\n} catch (Exception ignored) {}\n// preferred: keep your own AtomicBoolean mirroring factory.close()","typeGuard":"static boolean isOpen(FrameworkHttpClientFactory f, java.util.concurrent.atomic.AtomicBoolean ownedClosed) {\n    return !ownedClosed.get();\n}","tryCatchPattern":"try {\n    client = factory.newHttpClient(config);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"is closed\")) {\n        factory = createNewFactory(); // recreate and retry once\n        client = factory.newHttpClient(config);\n    } else throw e;\n}","preventionTips":["Treat the factory as single-lifecycle: one factory per client, closed together.","Track factory state in an AtomicBoolean and check before newHttpClient.","Avoid sharing one factory across restart/teardown logic."],"tags":["lifecycle","illegal-state","http-client","resource-closed"],"backgroundTag":"factory-already-closed","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"}