Merge pull request #6111 from dmatej/classanalyzer-race-backport-31

Classanalyzer race condition fix backport for 3.1
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 b072c1d..67ff45e 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,5 +1,6 @@
 /*
- * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
+ * 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.
  *
  * This program and the accompanying materials are made available under the
@@ -417,67 +418,34 @@
             runtimeCfgState.markAsShared();
 
             final InjectionManager injectionManager = findInjectionManager();
-            injectionManager.register(new ClientBinder(runtimeCfgState.getProperties()));
-
-            final ClientBootstrapBag bootstrapBag = new ClientBootstrapBag();
-            bootstrapBag.setManagedObjectsFinalizer(new ManagedObjectsFinalizer(injectionManager));
-
-            final ClientMessageBodyFactory.MessageBodyWorkersConfigurator messageBodyWorkersConfigurator =
-                    new ClientMessageBodyFactory.MessageBodyWorkersConfigurator();
-
-            List<BootstrapConfigurator> bootstrapConfigurators = Arrays.asList(
-                    new RequestScope.RequestScopeConfigurator(),
-                    new ParamConverterConfigurator(),
-                    new ParameterUpdaterConfigurator(),
-                    new RuntimeConfigConfigurator(runtimeCfgState),
-                    new ContextResolverFactory.ContextResolversConfigurator(),
-                    messageBodyWorkersConfigurator,
-                    new ExceptionMapperFactory.ExceptionMappersConfigurator(),
-                    new JaxrsProviders.ProvidersConfigurator(),
-                    new AutoDiscoverableConfigurator(RuntimeType.CLIENT),
-                    new ClientComponentConfigurator(),
-                    new FeatureConfigurator(RuntimeType.CLIENT));
-            bootstrapConfigurators.forEach(configurator -> configurator.init(injectionManager, bootstrapBag));
-
-            // AutoDiscoverable.
-            if (!CommonProperties.getValue(runtimeCfgState.getProperties(), RuntimeType.CLIENT,
-                    CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE, Boolean.FALSE, Boolean.class)) {
-                runtimeCfgState.configureAutoDiscoverableProviders(injectionManager, bootstrapBag.getAutoDiscoverables());
-            } else {
-                runtimeCfgState.configureForcedAutoDiscoverableProviders(injectionManager);
-            }
-
-            // Configure binders and features.
-            runtimeCfgState.configureMetaProviders(injectionManager, bootstrapBag.getManagedObjectsFinalizer());
-
-            // Bind providers.
-            final Collection<ComponentProvider> componentProviders = bootstrapBag.getComponentProviders().get();
-            ProviderBinder.bindProviders(
-                    runtimeCfgState.getComponentBag(), RuntimeType.CLIENT, null, injectionManager, componentProviders
-            );
-
-            ClientExecutorProvidersConfigurator executorProvidersConfigurator =
-                    new ClientExecutorProvidersConfigurator(runtimeCfgState.getComponentBag(),
-                            runtimeCfgState.client,
-                            this.executorService,
-                            this.scheduledExecutorService);
-            executorProvidersConfigurator.init(injectionManager, bootstrapBag);
+            final PreInitialization preInit =
+                    new PreInitialization(runtimeCfgState, injectionManager, this.executorService, this.scheduledExecutorService);
+            List<BootstrapConfigurator> bootstrapConfigurators = preInit.bootstrapConfigurators;
 
             injectionManager.completeRegistration();
 
-            bootstrapConfigurators.forEach(configurator -> configurator.postInit(injectionManager, 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, bootstrapBag);
+            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);
-            messageBodyWorkersConfigurator.setClientRuntime(crt);
+            preInit.messageBodyWorkersConfigurator.setClientRuntime(crt);
 
             return crt;
         }
 
-        private final InjectionManager findInjectionManager() {
+        private InjectionManager findInjectionManager() {
             try {
                 return Injections.createInjectionManager(RuntimeType.CLIENT);
             } catch (IllegalStateException ise) {
@@ -515,6 +483,69 @@
         }
     }
 
+    /* package */ static class PreInitialization {
+        private final ClientBootstrapBag bootstrapBag;
+        private final List<BootstrapConfigurator> bootstrapConfigurators;
+        private final ClientMessageBodyFactory.MessageBodyWorkersConfigurator messageBodyWorkersConfigurator;
+        private final ClientComponentConfigurator clientComponentConfigurator;
+
+        /* package */ PreInitialization(InjectionManager injectionManager) {
+            this(new State(new JerseyClient()), injectionManager, null, null);
+        }
+
+
+        /* package */ PreInitialization(
+                State runtimeCfgState,
+                InjectionManager injectionManager,
+                ExecutorService executorService,
+                ScheduledExecutorService scheduledExecutorService) {
+            injectionManager.register(new ClientBinder(runtimeCfgState.getProperties()));
+
+            bootstrapBag = new ClientBootstrapBag();
+            bootstrapBag.setManagedObjectsFinalizer(new ManagedObjectsFinalizer(injectionManager));
+
+            messageBodyWorkersConfigurator = new ClientMessageBodyFactory.MessageBodyWorkersConfigurator(); // 2020
+            clientComponentConfigurator = new ClientComponentConfigurator();
+
+            bootstrapConfigurators = Arrays.asList(new RequestScope.RequestScopeConfigurator(),
+                    new ParamConverterConfigurator(), // 2010
+                    new ParameterUpdaterConfigurator(), // 2011
+                    new RuntimeConfigConfigurator(runtimeCfgState), // 2012
+                    new ContextResolverFactory.ContextResolversConfigurator(), // 2014
+                    messageBodyWorkersConfigurator,
+                    new ExceptionMapperFactory.ExceptionMappersConfigurator(), // 2015
+                    new JaxrsProviders.ProvidersConfigurator(), // 2016
+                    new AutoDiscoverableConfigurator(RuntimeType.CLIENT),
+                    clientComponentConfigurator,
+                    new FeatureConfigurator(RuntimeType.CLIENT));
+            bootstrapConfigurators.forEach(configurator -> configurator.init(injectionManager, bootstrapBag));
+
+            // AutoDiscoverable.
+            if (!CommonProperties.getValue(runtimeCfgState.getProperties(), RuntimeType.CLIENT,
+                    CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE, Boolean.FALSE, Boolean.class)) {
+                runtimeCfgState.configureAutoDiscoverableProviders(injectionManager, bootstrapBag.getAutoDiscoverables());
+            } else {
+                runtimeCfgState.configureForcedAutoDiscoverableProviders(injectionManager);
+            }
+
+            // Configure binders and features.
+            runtimeCfgState.configureMetaProviders(injectionManager, bootstrapBag.getManagedObjectsFinalizer());
+
+            // Bind providers.
+            final Collection<ComponentProvider> componentProviders = bootstrapBag.getComponentProviders().get();
+            ProviderBinder.bindProviders(
+                    runtimeCfgState.getComponentBag(), RuntimeType.CLIENT, null, injectionManager, componentProviders
+            );
+
+            ClientExecutorProvidersConfigurator executorProvidersConfigurator =
+                    new ClientExecutorProvidersConfigurator(runtimeCfgState.getComponentBag(),
+                            runtimeCfgState.client,
+                            executorService,
+                            scheduledExecutorService);
+            executorProvidersConfigurator.init(injectionManager, bootstrapBag);
+        }
+    }
+
     /**
      * Construct a new Jersey configuration instance with the default features
      * and property values.
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 45fc11b..642a83a 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.
  *
@@ -144,6 +145,10 @@
 
     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<InjectionManager> effectiveInjectionManager = new ThreadLocal<>();
+
     public CdiComponentProvider() {
         customHk2TypesProvider = CdiUtil.lookupService(Hk2CustomBoundTypesProvider.class);
         injectionManagerStore = CdiUtil.createHk2InjectionManagerStore();
@@ -253,6 +258,14 @@
 
     @Override
     public void done() {
+
+        if (beanManager != null) {
+            final CdiComponentProvider extension = beanManager.getExtension(CdiComponentProvider.class);
+            if (extension != null) {
+                extension.effectiveInjectionManager.remove();
+            }
+        }
+
         if (requestScopedComponents.size() > 0) {
             InstanceBinding<ForeignRequestScopeBridge> descriptor = Bindings
                     .service((ForeignRequestScopeBridge) () -> requestScopedComponents)
@@ -656,7 +669,6 @@
     /* package */ abstract class InjectionManagerInjectedCdiTarget implements InjectionManagerInjectedTarget {
 
         private final InjectionTarget delegate;
-        private volatile InjectionManager effectiveInjectionManager;
 
         public InjectionManagerInjectedCdiTarget(InjectionTarget delegate) {
             this.delegate = delegate;
@@ -669,17 +681,20 @@
         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);
             }
 
-            delegate.inject(t, cc); // here the injection manager is used in HK2Bean
+            try {
+                delegate.inject(t, cc); // here the injection manager is used in HK2Bean
 
-            if (injectingManager != null) {
-                injectingManager.inject(t, CdiComponentProvider.CDI_CLASS_ANALYZER);
+                if (injectingManager != null) {
+                    injectingManager.inject(t, CdiComponentProvider.CDI_CLASS_ANALYZER);
+                }
+            } finally {
+                threadInjectionManagers.remove();
+                effectiveInjectionManager.remove();
             }
-
-            threadInjectionManagers.remove();
         }
 
         @Override
@@ -704,7 +719,16 @@
 
         @Override
         public void setInjectionManager(final InjectionManager injectionManager) {
-            this.effectiveInjectionManager = 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.");
+            }
         }
     }