From eb6b1ffa81af8ab20da9c33f34455e6eaf5c1fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Mon, 8 Apr 2024 15:17:29 +0200 Subject: [PATCH 1/4] Make a custom ContextManagerProvider with a single manager Because Quarkus doesn't care about one-per CL and Franz said this is a bottleneck for some reason --- .../QuarkusContextManagerProvider.java | 42 +++++++++++++++++++ .../SmallRyeContextPropagationRecorder.java | 3 +- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/QuarkusContextManagerProvider.java diff --git a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/QuarkusContextManagerProvider.java b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/QuarkusContextManagerProvider.java new file mode 100644 index 0000000000000..9f334e8c3fc7c --- /dev/null +++ b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/QuarkusContextManagerProvider.java @@ -0,0 +1,42 @@ +package io.quarkus.smallrye.context.runtime; + +import org.eclipse.microprofile.context.spi.ContextManager; + +import io.smallrye.context.SmallRyeContextManager; +import io.smallrye.context.SmallRyeContextManagerProvider; + +/** + * Quarkus doesn't need one manager per CL, we only have the one + */ +public class QuarkusContextManagerProvider extends SmallRyeContextManagerProvider { + + private SmallRyeContextManager contextManager; + + @Override + public SmallRyeContextManager getContextManager(ClassLoader classLoader) { + return contextManager; + } + + @Override + public SmallRyeContextManager getContextManager() { + return contextManager; + } + + @Override + public ContextManager findContextManager(ClassLoader classLoader) { + return contextManager; + } + + @Override + public void registerContextManager(ContextManager manager, ClassLoader classLoader) { + if (manager instanceof SmallRyeContextManager == false) { + throw new IllegalArgumentException("Only instances of SmallRyeContextManager are supported: " + manager); + } + contextManager = (SmallRyeContextManager) manager; + } + + @Override + public void releaseContextManager(ContextManager manager) { + contextManager = null; + } +} diff --git a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java index b1a40ce4a00c7..93a2d0d3cbc37 100644 --- a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java +++ b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java @@ -14,7 +14,6 @@ import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import io.smallrye.context.SmallRyeContextManager; -import io.smallrye.context.SmallRyeContextManagerProvider; import io.smallrye.context.SmallRyeManagedExecutor; import io.smallrye.context.SmallRyeThreadContext; @@ -31,7 +30,7 @@ public void configureStaticInit(List discoveredProviders, // build the manager at static init time // in the live-reload mode, the provider instance may be already set in the previous start if (ContextManagerProvider.INSTANCE.get() == null) { - ContextManagerProvider contextManagerProvider = new SmallRyeContextManagerProvider(); + ContextManagerProvider contextManagerProvider = new QuarkusContextManagerProvider(); ContextManagerProvider.register(contextManagerProvider); } From ad06f24dbce939bae0c6d7344318cd2a93b96f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Tue, 9 Apr 2024 15:03:33 +0200 Subject: [PATCH 2/4] Make sure OIDC is set up after CP It uses Mutiny, which requires CP to be initialised --- .../io/quarkus/oidc/deployment/OidcBuildStep.java | 5 ++++- .../ContextPropagationInitializedBuildItem.java | 11 +++++++++++ .../SmallRyeContextPropagationProcessor.java | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/ContextPropagationInitializedBuildItem.java diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java index b4827b8306bf4..9bc2cae847574 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java @@ -65,6 +65,7 @@ import io.quarkus.oidc.runtime.TenantConfigBean; import io.quarkus.oidc.runtime.providers.AzureAccessTokenCustomizer; import io.quarkus.runtime.TlsConfig; +import io.quarkus.smallrye.context.deployment.ContextPropagationInitializedBuildItem; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; import io.quarkus.vertx.http.deployment.EagerSecurityInterceptorBindingBuildItem; import io.quarkus.vertx.http.deployment.HttpAuthMechanismAnnotationBuildItem; @@ -220,7 +221,9 @@ public SyntheticBeanBuildItem setup( OidcConfig config, OidcRecorder recorder, CoreVertxBuildItem vertxBuildItem, - TlsConfig tlsConfig) { + TlsConfig tlsConfig, + // this is required for setup ordering: we need CP set up + ContextPropagationInitializedBuildItem cpInitializedBuildItem) { return SyntheticBeanBuildItem.configure(TenantConfigBean.class).unremovable().types(TenantConfigBean.class) .supplier(recorder.setup(config, vertxBuildItem.getVertx(), tlsConfig)) .destroyer(TenantConfigBean.Destroyer.class) diff --git a/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/ContextPropagationInitializedBuildItem.java b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/ContextPropagationInitializedBuildItem.java new file mode 100644 index 0000000000000..534494892a3a4 --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/ContextPropagationInitializedBuildItem.java @@ -0,0 +1,11 @@ +package io.quarkus.smallrye.context.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Marker build item for build ordering. Signifies that CP is set up + * and ready for use. + */ +public final class ContextPropagationInitializedBuildItem extends SimpleBuildItem { + +} diff --git a/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java index f923fc6dcaf70..341986949891a 100644 --- a/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java +++ b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java @@ -96,6 +96,7 @@ void buildStatic(SmallRyeContextPropagationRecorder recorder, List cpInitializedBuildItem, BuildProducer feature, BuildProducer syntheticBeans) { feature.produce(new FeatureBuildItem(Feature.SMALLRYE_CONTEXT_PROPAGATION)); @@ -111,6 +112,8 @@ void build(SmallRyeContextPropagationRecorder recorder, .unremovable() .supplier(recorder.initializeManagedExecutor(executorBuildItem.getExecutorProxy())) .setRuntimeInit().done()); + + cpInitializedBuildItem.produce(new ContextPropagationInitializedBuildItem()); } // transform IPs for ManagedExecutor/ThreadContext that use config annotation and don't yet have @NamedInstance From b9cc3c2dc65a6f61641c83a940e13c116ce6cd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Tue, 9 Apr 2024 15:03:56 +0200 Subject: [PATCH 3/4] Delay FT class init at runtime so CP is initialised --- .../deployment/SmallRyeFaultToleranceProcessor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index 345c8808dc85f..e15fb3ad077d0 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -50,6 +50,7 @@ import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; import io.quarkus.deployment.recording.RecorderContext; @@ -87,7 +88,8 @@ public void build(BuildProducer annotationsTran CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer reflectiveClass, BuildProducer reflectiveMethod, - BuildProducer config) { + BuildProducer config, + BuildProducer runtimeInitializedClassBuildItems) { feature.produce(new FeatureBuildItem(Feature.SMALLRYE_FAULT_TOLERANCE)); @@ -95,6 +97,8 @@ public void build(BuildProducer annotationsTran ContextPropagationRequestContextControllerProvider.class.getName())); serviceProvider.produce(new ServiceProviderBuildItem(RunnableWrapper.class.getName(), ContextPropagationRunnableWrapper.class.getName())); + // make sure this is initialised at runtime, otherwise it will get a non-initialised ContextPropagationManager + runtimeInitializedClassBuildItems.produce(new RuntimeInitializedClassBuildItem(RunnableWrapper.class.getName())); IndexView index = combinedIndexBuildItem.getIndex(); From 08ba1d14ea05421c496ea33ee9359d8269f1cef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 10 Apr 2024 15:13:31 +0200 Subject: [PATCH 4/4] CP: set up a temporary no-context/no-executor ContextManagerProvider during boot Because some extensions (only known one is spring-cloud-config-client) use Vert.x via Mutiny before runtime init is done. Mutiny and CP are both not set up yet, due to missing executor, but I'm starting CP with zero contexts and an executor which throws with an explicit error message, which should be enough to boot until runtime is properly set up. --- .../SmallRyeContextPropagationRecorder.java | 105 +++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java index 93a2d0d3cbc37..4955a2bbf54b7 100644 --- a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java +++ b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java @@ -1,11 +1,18 @@ package io.quarkus.smallrye.context.runtime; +import java.util.Collection; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import org.eclipse.microprofile.context.ManagedExecutor; import org.eclipse.microprofile.context.ThreadContext; +import org.eclipse.microprofile.context.spi.ContextManager.Builder; import org.eclipse.microprofile.context.spi.ContextManagerExtension; import org.eclipse.microprofile.context.spi.ContextManagerProvider; import org.eclipse.microprofile.context.spi.ThreadContextProvider; @@ -23,6 +30,92 @@ @Recorder public class SmallRyeContextPropagationRecorder { + private static final ExecutorService NOPE_EXECUTOR_SERVICE = new ExecutorService() { + + @Override + public void execute(Runnable command) { + nope(); + } + + @Override + public void shutdown() { + nope(); + } + + @Override + public List shutdownNow() { + nope(); + return null; + } + + @Override + public boolean isShutdown() { + nope(); + return false; + } + + @Override + public boolean isTerminated() { + nope(); + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + nope(); + return false; + } + + @Override + public Future submit(Callable task) { + nope(); + return null; + } + + @Override + public Future submit(Runnable task, T result) { + nope(); + return null; + } + + @Override + public Future submit(Runnable task) { + nope(); + return null; + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + nope(); + return null; + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + nope(); + return null; + } + + @Override + public T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + nope(); + return null; + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + nope(); + return null; + } + + private void nope() { + throw new RuntimeException( + "Trying to invoke ContextPropagation on a partially-configured ContextManager instance. You should wait until runtime init is done. You can do that by consuming the ContextPropagationBuildItem."); + } + }; private static SmallRyeContextManager.Builder builder; public void configureStaticInit(List discoveredProviders, @@ -39,6 +132,16 @@ public void configureStaticInit(List discoveredProviders, .getContextManagerBuilder(); builder.withThreadContextProviders(discoveredProviders.toArray(new ThreadContextProvider[0])); builder.withContextManagerExtensions(discoveredExtensions.toArray(new ContextManagerExtension[0])); + + // During boot, if anyone is using CP, they will get no propagation and an error if they try to use + // the executor. This is (so far) only for spring-cloud-config-client which uses Vert.x via Mutiny + // to load config before we're ready for runtime init + SmallRyeContextManager.Builder noContextBuilder = (SmallRyeContextManager.Builder) ContextManagerProvider.instance() + .getContextManagerBuilder(); + noContextBuilder.withThreadContextProviders(new ThreadContextProvider[0]); + noContextBuilder.withContextManagerExtensions(new ContextManagerExtension[0]); + noContextBuilder.withDefaultExecutorService(NOPE_EXECUTOR_SERVICE); + ContextManagerProvider.instance().registerContextManager(noContextBuilder.build(), null /* not used */); } public void configureRuntime(ExecutorService executorService, ShutdownContext shutdownContext) { @@ -58,7 +161,7 @@ public void run() { } }); //Avoid leaking the classloader: - this.builder = null; + SmallRyeContextPropagationRecorder.builder = null; } public Supplier initializeManagedExecutor(ExecutorService executorService) {