From 02e10fa849a6ca697a0ec7d89e341f75e18951f3 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 10 Jan 2024 11:55:36 +0100 Subject: [PATCH] ArC: introduce quarkus.arc.optimize-contexts=auto - if "auto" is used then only optimize if there is less than 1000 beans in the app; removed beans are excluded - use this as the default value --- .../io/quarkus/arc/deployment/ArcConfig.java | 14 ++++++-- .../quarkus/arc/deployment/ArcProcessor.java | 20 +++++++++-- .../optimized/OptimizeContextsAutoTest.java | 35 +++++++++++++++++++ .../OptimizeContextsDisabledTest.java | 34 ++++++++++++++++++ .../test/context/optimized/SimpleBean.java | 12 +++++++ .../io/quarkus/arc/runtime/ArcRecorder.java | 4 +-- .../quarkus/arc/processor/BeanProcessor.java | 28 +++++++++++---- 7 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsAutoTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsDisabledTest.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/SimpleBean.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java index 1c9b1f4c01629..5b8c1893fd1f7 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java @@ -224,13 +224,21 @@ public class ArcConfig { public ArcContextPropagationConfig contextPropagation; /** - * If set to {@code true}, the container should try to optimize the contexts for some of the scopes. + * If set to {@code true}, the container should try to optimize the contexts for some of the scopes. If set to {@code auto} + * then optimize the contexts if there's less than 1000 beans in the application. If set to {@code false} do not optimize + * the contexts. *

* Typically, some implementation parts of the context for {@link jakarta.enterprise.context.ApplicationScoped} could be * pregenerated during build. */ - @ConfigItem(defaultValue = "true", generateDocumentation = false) - public boolean optimizeContexts; + @ConfigItem(defaultValue = "auto", generateDocumentation = false) + public OptimizeContexts optimizeContexts; + + public enum OptimizeContexts { + TRUE, + FALSE, + AUTO + } public final boolean isRemoveUnusedBeansFieldValid() { return ALLOWED_REMOVE_UNUSED_BEANS_VALUES.contains(removeUnusedBeans.toLowerCase()); diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index dc44f9333bcc9..0ad8d634aac41 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -396,7 +396,23 @@ public Integer compute(AnnotationTarget target, Collection stere } builder.setBuildCompatibleExtensions(buildCompatibleExtensions.entrypoint); - builder.setOptimizeContexts(arcConfig.optimizeContexts); + builder.setOptimizeContexts(new Predicate() { + @Override + public boolean test(BeanDeployment deployment) { + switch (arcConfig.optimizeContexts) { + case TRUE: + return true; + case FALSE: + return false; + case AUTO: + // Optimize the context if there is less than 1000 beans in the app + // Note that removed beans are excluded + return deployment.getBeans().size() < 1000; + default: + throw new IllegalArgumentException("Unexpected value: " + arcConfig.optimizeContexts); + } + } + }); BeanProcessor beanProcessor = builder.build(); ContextRegistrar.RegistrationContext context = beanProcessor.registerCustomContexts(); @@ -598,7 +614,7 @@ public ArcContainerBuildItem initializeContainer(ArcConfig config, ArcRecorder r throws Exception { ArcContainer container = recorder.initContainer(shutdown, currentContextFactory.isPresent() ? currentContextFactory.get().getFactory() : null, - config.strictCompatibility, config.optimizeContexts); + config.strictCompatibility); return new ArcContainerBuildItem(container); } diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsAutoTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsAutoTest.java new file mode 100644 index 0000000000000..666c38dbc79f3 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsAutoTest.java @@ -0,0 +1,35 @@ +package io.quarkus.arc.test.context.optimized; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ServiceLoader; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.ComponentsProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OptimizeContextsAutoTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(SimpleBean.class)) + .overrideConfigKey("quarkus.arc.optimize-contexts", "auto"); + + @Inject + SimpleBean bean; + + @Test + public void testContexts() { + assertTrue(bean.ping()); + for (ComponentsProvider componentsProvider : ServiceLoader.load(ComponentsProvider.class)) { + // We have less than 1000 beans + assertFalse(componentsProvider.getComponents().getContextInstances().isEmpty()); + } + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsDisabledTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsDisabledTest.java new file mode 100644 index 0000000000000..b1b611c81312c --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/OptimizeContextsDisabledTest.java @@ -0,0 +1,34 @@ +package io.quarkus.arc.test.context.optimized; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ServiceLoader; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.ComponentsProvider; +import io.quarkus.test.QuarkusUnitTest; + +public class OptimizeContextsDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(SimpleBean.class)) + .overrideConfigKey("quarkus.arc.optimize-contexts", "false"); + + @Inject + SimpleBean bean; + + @Test + public void testContexts() { + assertTrue(bean.ping()); + for (ComponentsProvider componentsProvider : ServiceLoader.load(ComponentsProvider.class)) { + assertTrue(componentsProvider.getComponents().getContextInstances().isEmpty()); + } + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/SimpleBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/SimpleBean.java new file mode 100644 index 0000000000000..0c545a000a5b0 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/context/optimized/SimpleBean.java @@ -0,0 +1,12 @@ +package io.quarkus.arc.test.context.optimized; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +class SimpleBean { + + public boolean ping() { + return true; + } + +} \ No newline at end of file diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java index 23ffb5196720d..4c1ecac85a712 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcRecorder.java @@ -42,12 +42,10 @@ public class ArcRecorder { public static volatile Map, ?>> syntheticBeanProviders; public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue currentContextFactory, - boolean strictCompatibility, boolean optimizeContexts) - throws Exception { + boolean strictCompatibility) throws Exception { ArcInitConfig.Builder builder = ArcInitConfig.builder(); builder.setCurrentContextFactory(currentContextFactory != null ? currentContextFactory.getValue() : null); builder.setStrictCompatibility(strictCompatibility); - builder.setOptimizeContexts(optimizeContexts); ArcContainer container = Arc.initialize(builder.build()); shutdown.addShutdownTask(new Runnable() { @Override diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index beb80d5153a80..b15c310e4b1cd 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -78,7 +78,7 @@ public static Builder builder() { private final boolean generateSources; private final boolean allowMocking; private final boolean transformUnproxyableClasses; - private final boolean optimizeContexts; + private final Predicate optimizeContexts; private final List>> suppressConditionGenerators; // This predicate is used to filter annotations for InjectionPoint metadata @@ -187,6 +187,7 @@ public List generateResources(ReflectionRegistration reflectionRegistr ReflectionRegistration refReg = reflectionRegistration != null ? reflectionRegistration : this.reflectionRegistration; PrivateMembersCollector privateMembers = new PrivateMembersCollector(); + boolean optimizeContextsValue = optimizeContexts != null ? optimizeContexts.test(beanDeployment) : false; // These maps are precomputed and then used in the ComponentsProviderGenerator which is generated first Map beanToGeneratedName = new HashMap<>(); @@ -240,7 +241,7 @@ public List generateResources(ReflectionRegistration reflectionRegistr ContextInstancesGenerator contextInstancesGenerator = new ContextInstancesGenerator(generateSources, refReg, beanDeployment, scopeToGeneratedName); - if (optimizeContexts) { + if (optimizeContextsValue) { contextInstancesGenerator.precomputeGeneratedName(BuiltinScope.APPLICATION.getName()); contextInstancesGenerator.precomputeGeneratedName(BuiltinScope.REQUEST.getName()); } @@ -364,7 +365,7 @@ public Collection call() throws Exception { })); } - if (optimizeContexts) { + if (optimizeContextsValue) { // Generate _ContextInstances primaryTasks.add(executor.submit(new Callable>() { @@ -450,7 +451,7 @@ public Collection call() throws Exception { observerToGeneratedName, scopeToGeneratedName)); - if (optimizeContexts) { + if (optimizeContextsValue) { // Generate _ContextInstances resources.addAll(contextInstancesGenerator.generate(BuiltinScope.APPLICATION.getName())); resources.addAll(contextInstancesGenerator.generate(BuiltinScope.REQUEST.getName())); @@ -564,7 +565,7 @@ public static class Builder { boolean failOnInterceptedPrivateMethod; boolean allowMocking; boolean strictCompatibility; - boolean optimizeContexts; + Predicate optimizeContexts; AlternativePriorities alternativePriorities; final List> excludeTypes; @@ -600,7 +601,6 @@ public Builder() { failOnInterceptedPrivateMethod = false; allowMocking = false; strictCompatibility = false; - optimizeContexts = false; excludeTypes = new ArrayList<>(); @@ -842,7 +842,21 @@ public Builder setStrictCompatibility(boolean strictCompatibility) { * @return self */ public Builder setOptimizeContexts(boolean value) { - this.optimizeContexts = value; + return setOptimizeContexts(new Predicate() { + @Override + public boolean test(BeanDeployment t) { + return value; + } + }); + } + + /** + * + * @param fun + * @return self + */ + public Builder setOptimizeContexts(Predicate fun) { + this.optimizeContexts = fun; return this; }