From 5b5e668d67b472215a1e19d2107d4a024c5cf64d Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Sun, 11 Jan 2026 16:54:01 +0100 Subject: [PATCH 1/3] Fix a race condition Happens when many threads execute REST client call at the same. Symptom: Occasional IllegalStateException: Could not find an implementation of ClassAnalyzer with name CdiInjecteeSkippingClassAnalyzer from WebTarget.request Reason for the exception: * The injectionManager is set earlier than it's used, it's stored to a shared volatile variable and later picked up * If some thread sets the shared volatile variable to another injectionManager, the original thread would use another threads injectionManager * In case the second thread doesn't bind the ClassAnalyzer class in time before the first thread uses it, the first thread won't have ClassAnalyzer bean available and throws exception --- .../ext/cdi1x/internal/CdiComponentProvider.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java index 4e3627bc816..d8e4c665f91 100644 --- a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java +++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2026 Contributors to the Eclipse Foundation * Copyright (c) 2013, 2025 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2022 Payara Foundation and/or its affiliates. All rights reserved. * @@ -145,6 +146,10 @@ public Boolean apply(final Class clazz) { private boolean initialized = false; + // Shared by multiple clients, which can be initialized in parallel, + // therefore storing to a thread local and removing after initialization done in the done() method + private ThreadLocal effectiveInjectionManager = new ThreadLocal<>(); + public CdiComponentProvider() { customHk2TypesProvider = CdiUtil.lookupService(Hk2CustomBoundTypesProvider.class); injectionManagerStore = CdiUtil.createHk2InjectionManagerStore(); @@ -259,6 +264,7 @@ private boolean bind(final Class clazz, final Set> providerContracts @Override public void done() { + effectiveInjectionManager.remove(); if (requestScopedComponents.size() > 0) { InstanceBinding descriptor = Bindings .service((ForeignRequestScopeBridge) () -> requestScopedComponents) @@ -658,7 +664,6 @@ private StringBuilder listElements(final StringBuilder logMsgBuilder, final Coll /* package */ abstract class InjectionManagerInjectedCdiTarget implements InjectionManagerInjectedTarget { private final InjectionTarget delegate; - private volatile InjectionManager effectiveInjectionManager; public InjectionManagerInjectedCdiTarget(InjectionTarget delegate) { this.delegate = delegate; @@ -671,7 +676,7 @@ public InjectionManagerInjectedCdiTarget(InjectionTarget delegate) { public void inject(final Object t, final CreationalContext cc) { InjectionManager injectingManager = getEffectiveInjectionManager(); if (injectingManager == null || /* reload */ injectingManager.isShutdown()) { - injectingManager = effectiveInjectionManager; + injectingManager = effectiveInjectionManager.get(); threadInjectionManagers.set(injectingManager); } @@ -706,7 +711,7 @@ public void dispose(final Object t) { @Override public void setInjectionManager(final InjectionManager injectionManager) { - this.effectiveInjectionManager = injectionManager; + effectiveInjectionManager.set(injectionManager); } } From dceaeaf3528746b049b2bfd690972cf63db68667 Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Tue, 13 Jan 2026 18:32:56 +0100 Subject: [PATCH 2/3] Improve previous fix Remove the thread local from the correct component (same as retrieved in the CdiComponentProvider.initialize method) Save a few nanoseconds by calling get instead of set on the thread local, log an error if a leaking injectionManager detected. --- .../cdi1x/internal/CdiComponentProvider.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java index d8e4c665f91..95d85e6c555 100644 --- a/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java +++ b/ext/cdi/jersey-cdi1x/src/main/java/org/glassfish/jersey/ext/cdi1x/internal/CdiComponentProvider.java @@ -264,7 +264,14 @@ private boolean bind(final Class clazz, final Set> providerContracts @Override public void done() { - effectiveInjectionManager.remove(); + + if (beanManager != null) { + final CdiComponentProvider extension = beanManager.getExtension(CdiComponentProvider.class); + if (extension != null) { + extension.effectiveInjectionManager.remove(); + } + } + if (requestScopedComponents.size() > 0) { InstanceBinding descriptor = Bindings .service((ForeignRequestScopeBridge) () -> requestScopedComponents) @@ -711,7 +718,16 @@ public void dispose(final Object t) { @Override public void setInjectionManager(final InjectionManager injectionManager) { - effectiveInjectionManager.set(injectionManager); + // The injectionManager is always the same within a single invocation. + // We set it only once, which is a bit faster than calling set with the same value + final InjectionManager manager = effectiveInjectionManager.get(); + if (manager == null) { + effectiveInjectionManager.set(injectionManager); + } else if (!manager.equals(injectionManager)) { + LOGGER.log(Level.SEVERE, "Leaking injection manager found in the current thread." + + " Very likely because it wasn't removed in a previous invocation and" + + " leaked into this invocation."); + } } } From 6189f019ab68147bff7592545d61fda9270972b8 Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Wed, 14 Jan 2026 09:15:45 +0100 Subject: [PATCH 3/3] Fix of leaking thread local If ClientRuntime needs CDI beans, it initializes CdiComponentProvider again, we need to clean up later. --- .../glassfish/jersey/client/ClientConfig.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java index 53cc138518b..54cf97f9bd0 100644 --- a/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java +++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2026 Contributors to the Eclipse Foundation * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 Payara Foundation and/or its affiliates. * @@ -425,12 +426,21 @@ private ClientRuntime initRuntime() { injectionManager.completeRegistration(); - bootstrapConfigurators.forEach(configurator -> configurator.postInit(injectionManager, preInit.bootstrapBag)); + bootstrapConfigurators.forEach(configurator -> { + if (!configurator.equals(preInit.clientComponentConfigurator)) { + configurator.postInit(injectionManager, preInit.bootstrapBag); + } + }); + final ClientConfig configuration = new ClientConfig(runtimeCfgState); final Connector connector = connectorProvider.getConnector(client, configuration); final ClientRuntime crt = new ClientRuntime(configuration, connector, injectionManager, preInit.bootstrapBag); + // We call postInit here to clean up thread locals, + // while other configurators need to be postInit earlier because they set up dependencies for ClientRuntime + preInit.clientComponentConfigurator.postInit(injectionManager, preInit.bootstrapBag); + client.registerShutdownHook(crt); preInit.messageBodyWorkersConfigurator.setClientRuntime(crt); @@ -479,6 +489,7 @@ public int hashCode() { private final ClientBootstrapBag bootstrapBag; private final List bootstrapConfigurators; private final ClientMessageBodyFactory.MessageBodyWorkersConfigurator messageBodyWorkersConfigurator; + private final ClientComponentConfigurator clientComponentConfigurator; /* package */ PreInitialization(InjectionManager injectionManager) { this(new State(new JerseyClient()), injectionManager, null, null); @@ -496,9 +507,9 @@ public int hashCode() { bootstrapBag.setManagedObjectsFinalizer(new ManagedObjectsFinalizer(injectionManager)); messageBodyWorkersConfigurator = new ClientMessageBodyFactory.MessageBodyWorkersConfigurator(); // 2020 + clientComponentConfigurator = new ClientComponentConfigurator(); - bootstrapConfigurators = Arrays.asList( - new RequestScope.RequestScopeConfigurator(), + bootstrapConfigurators = Arrays.asList(new RequestScope.RequestScopeConfigurator(), new ParamConverterConfigurator(), // 2010 new ParameterUpdaterConfigurator(), // 2011 new RuntimeConfigConfigurator(runtimeCfgState), // 2012 @@ -507,7 +518,7 @@ public int hashCode() { new ExceptionMapperFactory.ExceptionMappersConfigurator(), // 2015 new JaxrsProviders.ProvidersConfigurator(), // 2016 new AutoDiscoverableConfigurator(RuntimeType.CLIENT), - new ClientComponentConfigurator(), + clientComponentConfigurator, new FeatureConfigurator(RuntimeType.CLIENT)); bootstrapConfigurators.forEach(configurator -> configurator.init(injectionManager, bootstrapBag));