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 30f9356d1dbba..5c2fb21383232 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 @@ -223,6 +223,12 @@ public class ArcConfig { @ConfigItem public ArcContextPropagationConfig contextPropagation; + /** + * A hint that the container should try to optimize the contexts for some of the scopes. + */ + @ConfigItem(defaultValue = "true") + public boolean optimizeContexts; + 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 1821e039d595b..dc44f9333bcc9 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,6 +396,7 @@ public Integer compute(AnnotationTarget target, Collection stere } builder.setBuildCompatibleExtensions(buildCompatibleExtensions.entrypoint); + builder.setOptimizeContexts(arcConfig.optimizeContexts); BeanProcessor beanProcessor = builder.build(); ContextRegistrar.RegistrationContext context = beanProcessor.registerCustomContexts(); @@ -516,6 +517,12 @@ public void generateResources(ArcConfig config, ExecutorService executor = parallelResourceGeneration ? buildExecutor : null; List resources; resources = beanProcessor.generateResources(new ReflectionRegistration() { + + @Override + public void registerMethod(String declaringClass, String name, String... params) { + reflectiveMethods.produce(new ReflectiveMethodBuildItem(declaringClass, name, params)); + } + @Override public void registerMethod(MethodInfo methodInfo) { reflectiveMethods.produce(new ReflectiveMethodBuildItem(methodInfo)); @@ -591,7 +598,7 @@ public ArcContainerBuildItem initializeContainer(ArcConfig config, ArcRecorder r throws Exception { ArcContainer container = recorder.initContainer(shutdown, currentContextFactory.isPresent() ? currentContextFactory.get().getFactory() : null, - config.strictCompatibility); + config.strictCompatibility, config.optimizeContexts); return new ArcContainerBuildItem(container); } 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 e4af7ea639bb5..23ffb5196720d 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,11 +42,13 @@ public class ArcRecorder { public static volatile Map, ?>> syntheticBeanProviders; public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue currentContextFactory, - boolean strictCompatibility) + boolean strictCompatibility, boolean optimizeContexts) throws Exception { - ArcContainer container = Arc.initialize(ArcInitConfig.builder() - .setCurrentContextFactory(currentContextFactory != null ? currentContextFactory.getValue() : null) - .setStrictCompatibility(strictCompatibility).build()); + 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 public void run() { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 72eda2708f2ba..eb29b1dd49a65 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -57,7 +57,7 @@ public class BeanDeployment { private static final Logger LOGGER = Logger.getLogger(BeanDeployment.class); - private final String name; + final String name; private final BuildContextImpl buildContext; private final IndexView beanArchiveComputingIndex; 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 fb8ae68c42553..02a6491546b86 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,6 +78,7 @@ public static Builder builder() { private final boolean generateSources; private final boolean allowMocking; private final boolean transformUnproxyableClasses; + private final boolean optimizeContexts; private final List>> suppressConditionGenerators; // This predicate is used to filter annotations for InjectionPoint metadata @@ -107,6 +108,7 @@ private BeanProcessor(Builder builder) { applicationClassPredicate); this.generateSources = builder.generateSources; this.allowMocking = builder.allowMocking; + this.optimizeContexts = builder.optimizeContexts; this.transformUnproxyableClasses = builder.transformUnproxyableClasses; this.suppressConditionGenerators = builder.suppressConditionGenerators; @@ -189,6 +191,7 @@ public List generateResources(ReflectionRegistration reflectionRegistr // These maps are precomputed and then used in the ComponentsProviderGenerator which is generated first Map beanToGeneratedName = new HashMap<>(); Map observerToGeneratedName = new HashMap<>(); + Map scopeToGeneratedName = new HashMap<>(); BeanGenerator beanGenerator = new BeanGenerator(annotationLiterals, applicationClassPredicate, privateMembers, generateSources, refReg, existingClasses, beanToGeneratedName, @@ -229,6 +232,13 @@ public List generateResources(ReflectionRegistration reflectionRegistr observerGenerator.precomputeGeneratedName(observer); } + ContextInstancesGenerator contextInstancesGenerator = new ContextInstancesGenerator(generateSources, + reflectionRegistration, beanDeployment, scopeToGeneratedName); + if (optimizeContexts) { + contextInstancesGenerator.precomputeGeneratedName(BuiltinScope.APPLICATION.getName()); + contextInstancesGenerator.precomputeGeneratedName(BuiltinScope.REQUEST.getName()); + } + CustomAlterableContextsGenerator alterableContextsGenerator = new CustomAlterableContextsGenerator(generateSources); List alterableContexts = customAlterableContexts.getRegistered(); @@ -251,7 +261,8 @@ public Collection call() throws Exception { name, beanDeployment, beanToGeneratedName, - observerToGeneratedName); + observerToGeneratedName, + scopeToGeneratedName); } })); @@ -350,6 +361,20 @@ public Collection call() throws Exception { })); } + if (optimizeContexts) { + // Generate _ApplicationContextInstances + primaryTasks.add(executor.submit(new Callable>() { + + @Override + public Collection call() throws Exception { + Collection resources = new ArrayList<>(); + resources.addAll(contextInstancesGenerator.generate(BuiltinScope.APPLICATION.getName())); + resources.addAll(contextInstancesGenerator.generate(BuiltinScope.REQUEST.getName())); + return resources; + } + })); + } + for (Future> future : primaryTasks) { resources.addAll(future.get()); } @@ -419,7 +444,14 @@ public Collection call() throws Exception { name, beanDeployment, beanToGeneratedName, - observerToGeneratedName)); + observerToGeneratedName, + scopeToGeneratedName)); + + if (optimizeContexts) { + // Generate _ApplicationContextInstances + resources.addAll(contextInstancesGenerator.generate(BuiltinScope.APPLICATION.getName())); + resources.addAll(contextInstancesGenerator.generate(BuiltinScope.REQUEST.getName())); + } } // Generate AnnotationLiterals - at this point all annotation literals must be processed @@ -510,6 +542,7 @@ public static class Builder { boolean failOnInterceptedPrivateMethod; boolean allowMocking; boolean strictCompatibility; + boolean optimizeContexts; AlternativePriorities alternativePriorities; final List> excludeTypes; @@ -545,6 +578,7 @@ public Builder() { failOnInterceptedPrivateMethod = false; allowMocking = false; strictCompatibility = false; + optimizeContexts = false; excludeTypes = new ArrayList<>(); @@ -780,6 +814,16 @@ public Builder setStrictCompatibility(boolean strictCompatibility) { return this; } + /** + * + * @param value + * @return self + */ + public Builder setOptimizeContexts(boolean value) { + this.optimizeContexts = value; + return this; + } + /** * Can be used to compute a priority of an alternative bean. A non-null computed value always * takes precedence over the priority defined by {@link Priority} or a stereotype. diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java index 51e30cd865dc4..c3d6f77d6575f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ComponentsProviderGenerator.java @@ -70,10 +70,11 @@ public ComponentsProviderGenerator(AnnotationLiteralProcessor annotationLiterals * @param beanDeployment * @param beanToGeneratedName * @param observerToGeneratedName + * @param scopeToContextInstances * @return a collection of resources */ Collection generate(String name, BeanDeployment beanDeployment, Map beanToGeneratedName, - Map observerToGeneratedName) { + Map observerToGeneratedName, Map scopeToContextInstances) { ResourceClassOutput classOutput = new ResourceClassOutput(true, generateSources); @@ -162,11 +163,25 @@ Collection generate(String name, BeanDeployment beanDeployment, Map e : scopeToContextInstances.entrySet()) { + ResultHandle scope = getComponents.loadClass(e.getKey().toString()); + ResultHandle supplier = getComponents.invokeStaticMethod( + MethodDescriptor.ofMethod(Components.class, "newContextInstancesSupplier", Supplier.class, Class.class), + getComponents.loadClass(e.getValue())); + getComponents.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, contextInstances, scope, supplier); + } + } + ResultHandle componentsHandle = getComponents.newInstance( MethodDescriptor.ofConstructor(Components.class, Collection.class, Collection.class, Collection.class, - Set.class, Map.class, Supplier.class, Map.class, Set.class), + Set.class, Map.class, Supplier.class, Map.class, Set.class, Map.class), beansHandle, observersHandle, contextsHandle, interceptorBindings, transitiveBindingsHandle, - removedBeansSupplier.getInstance(), qualifiersNonbindingMembers, qualifiers); + removedBeansSupplier.getInstance(), qualifiersNonbindingMembers, qualifiers, contextInstances); getComponents.returnValue(componentsHandle); // Finally write the bytecode diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java new file mode 100644 index 0000000000000..9a6f2308ff053 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java @@ -0,0 +1,267 @@ +package io.quarkus.arc.processor; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_VOLATILE; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +import org.jboss.jandex.DotName; + +import io.quarkus.arc.ContextInstanceHandle; +import io.quarkus.arc.impl.ContextInstances; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.FieldCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.Switch.StringSwitch; +import io.quarkus.gizmo.TryBlock; + +public class ContextInstancesGenerator extends AbstractGenerator { + + static final String APP_CONTEXT_INSTANCES_SUFFIX = "_ContextInstances"; + + private final BeanDeployment beanDeployment; + private final Map scopeToGeneratedName; + + public ContextInstancesGenerator(boolean generateSources, ReflectionRegistration reflectionRegistration, + BeanDeployment beanDeployment, Map scopeToGeneratedName) { + super(generateSources, reflectionRegistration); + this.beanDeployment = beanDeployment; + this.scopeToGeneratedName = scopeToGeneratedName; + } + + void precomputeGeneratedName(DotName scope) { + String generatedName = DEFAULT_PACKAGE + "." + beanDeployment.name + UNDERSCORE + + scope.toString().replace(".", UNDERSCORE) + + APP_CONTEXT_INSTANCES_SUFFIX; + scopeToGeneratedName.put(scope, generatedName); + } + + Collection generate(DotName scope) { + List beans = new BeanStream(beanDeployment.getBeans()).withScope(scope).collect(); + ResourceClassOutput classOutput = new ResourceClassOutput(true, generateSources); + String generatedName = scopeToGeneratedName.get(scope); + reflectionRegistration.registerMethod(generatedName, MethodDescriptor.INIT); + + ClassCreator contextInstances = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(ContextInstances.class).build(); + + // Add fields for all beans + // The name of the field is a generated index + // For example: + // private volatile ContextInstanceHandle 1; + Map idToField = new HashMap<>(); + int fieldIndex = 0; + for (BeanInfo bean : beans) { + FieldCreator fc = contextInstances.getFieldCreator("" + fieldIndex++, ContextInstanceHandle.class) + .setModifiers(ACC_PRIVATE | ACC_VOLATILE); + idToField.put(bean.getIdentifier(), fc.getFieldDescriptor()); + } + + FieldCreator lockField = contextInstances.getFieldCreator("lock", Lock.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + + MethodCreator constructor = contextInstances.getMethodCreator(MethodDescriptor.INIT, "V"); + constructor.invokeSpecialMethod(MethodDescriptors.OBJECT_CONSTRUCTOR, constructor.getThis()); + constructor.writeInstanceField(lockField.getFieldDescriptor(), constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(ReentrantLock.class))); + constructor.returnVoid(); + + implementComputeIfAbsent(contextInstances, beans, idToField, + lockField.getFieldDescriptor()); + implementGetIfPresent(contextInstances, beans, idToField); + implementRemove(contextInstances, beans, idToField, lockField.getFieldDescriptor()); + implementClear(contextInstances, idToField, lockField.getFieldDescriptor()); + implementGetAllPresent(contextInstances, idToField, lockField.getFieldDescriptor()); + + contextInstances.close(); + + return classOutput.getResources(); + } + + private void implementGetAllPresent(ClassCreator contextInstances, Map idToField, + FieldDescriptor lockField) { + MethodCreator getAllPresent = contextInstances.getMethodCreator("getAllPresent", Set.class) + .setModifiers(ACC_PUBLIC); + // lock.lock(); + // try { + // Set> ret = new HashSet<>(); + // ContextInstanceHandle copy = this.1; + // if (copy != null) { + // ret.add(copy); + // } + // return ret; + // } catch(Throwable t) { + // lock.unlock(); + // throw t; + // } + ResultHandle lock = getAllPresent.readInstanceField(lockField, getAllPresent.getThis()); + getAllPresent.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + TryBlock tryBlock = getAllPresent.tryBlock(); + ResultHandle ret = tryBlock.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (FieldDescriptor field : idToField.values()) { + ResultHandle copy = tryBlock.readInstanceField(field, tryBlock.getThis()); + tryBlock.ifNotNull(copy).trueBranch().invokeInterfaceMethod(MethodDescriptors.SET_ADD, ret, copy); + } + tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + tryBlock.returnValue(ret); + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + catchBlock.throwException(catchBlock.getCaughtException()); + } + + private void implementClear(ClassCreator applicationContextInstances, Map idToField, + FieldDescriptor lockField) { + MethodCreator clear = applicationContextInstances.getMethodCreator("clear", void.class).setModifiers(ACC_PUBLIC); + // lock.lock(); + // try { + // this.1 = null; + // lock.unlock(); + // } catch(Throwable t) { + // lock.unlock(); + // throw t; + // } + ResultHandle lock = clear.readInstanceField(lockField, clear.getThis()); + clear.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + TryBlock tryBlock = clear.tryBlock(); + for (FieldDescriptor field : idToField.values()) { + tryBlock.writeInstanceField(field, tryBlock.getThis(), tryBlock.loadNull()); + } + tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + tryBlock.returnVoid(); + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + catchBlock.throwException(catchBlock.getCaughtException()); + } + + private void implementRemove(ClassCreator contextInstances, List applicationScopedBeans, + Map idToField, FieldDescriptor lockField) { + MethodCreator remove = contextInstances + .getMethodCreator("remove", ContextInstanceHandle.class, String.class) + .setModifiers(ACC_PUBLIC); + + StringSwitch strSwitch = remove.stringSwitch(remove.getMethodParam(0)); + strSwitch.fallThrough(); + for (BeanInfo bean : applicationScopedBeans) { + FieldDescriptor instanceField = idToField.get(bean.getIdentifier()); + + // There is a separate remove method for every bean instance field + // For example remove1() + MethodCreator removeBean = contextInstances.getMethodCreator("remove" + instanceField.getName(), + ContextInstanceHandle.class).setModifiers(ACC_PRIVATE); + // lock.lock(); + // try { + // ContextInstanceHandle copy = this.cb716f4af20404d2a005b55296d948c097023737; + // if (copy != null) { + // this.cb716f4af20404d2a005b55296d948c097023737 = null; + // } + // lock.unlock(); + // return copy; + // } catch(Throwable t) { + // lock.unlock(); + // throw t; + // } + + ResultHandle lock = removeBean.readInstanceField(lockField, removeBean.getThis()); + removeBean.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + TryBlock tryBlock = removeBean.tryBlock(); + ResultHandle copy = tryBlock.readInstanceField(instanceField, tryBlock.getThis()); + BytecodeCreator isNotNull = tryBlock.ifNotNull(copy).trueBranch(); + isNotNull.writeInstanceField(instanceField, isNotNull.getThis(), isNotNull.loadNull()); + tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + tryBlock.returnValue(copy); + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + catchBlock.throwException(catchBlock.getCaughtException()); + + strSwitch.caseOf(bean.getIdentifier(), bc -> { + bc.returnValue(bc.invokeVirtualMethod(removeBean.getMethodDescriptor(), bc.getThis())); + }); + } + strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); + } + + private void implementGetIfPresent(ClassCreator contextInstances, List applicationScopedBeans, + Map idToField) { + MethodCreator getIfPresent = contextInstances + .getMethodCreator("getIfPresent", ContextInstanceHandle.class, String.class) + .setModifiers(ACC_PUBLIC); + + StringSwitch strSwitch = getIfPresent.stringSwitch(getIfPresent.getMethodParam(0)); + for (BeanInfo bean : applicationScopedBeans) { + strSwitch.caseOf(bean.getIdentifier(), bc -> { + bc.returnValue(bc.readInstanceField(idToField.get(bean.getIdentifier()), bc.getThis())); + }); + } + strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); + } + + private void implementComputeIfAbsent(ClassCreator contextInstances, List applicationScopedBeans, + Map idToField, FieldDescriptor lockField) { + MethodCreator computeIfAbsent = contextInstances + .getMethodCreator("computeIfAbsent", ContextInstanceHandle.class, String.class, Supplier.class) + .setModifiers(ACC_PUBLIC); + + StringSwitch strSwitch = computeIfAbsent.stringSwitch(computeIfAbsent.getMethodParam(0)); + strSwitch.fallThrough(); + for (BeanInfo bean : applicationScopedBeans) { + FieldDescriptor instanceField = idToField.get(bean.getIdentifier()); + + // There is a separate compute method for every bean instance field + // For example compute1() + MethodCreator compute = contextInstances.getMethodCreator("compute" + instanceField.getName(), + ContextInstanceHandle.class, Supplier.class).setModifiers(ACC_PRIVATE); + // ContextInstanceHandle copy = this.cb716f4af20404d2a005b55296d948c097023737; + // if (copy != null) { + // return copy; + // } + // lock.lock(); + // try { + // if (this.cb716f4af20404d2a005b55296d948c097023737 == null) { + // this.cb716f4af20404d2a005b55296d948c097023737 = supplier.get(); + // } + // lock.unlock(); + // return this.cb716f4af20404d2a005b55296d948c097023737; + // } catch(Throwable t) { + // lock.unlock(); + // throw t; + // } + ResultHandle copy = compute.readInstanceField(instanceField, compute.getThis()); + compute.ifNotNull(copy).trueBranch().returnValue(copy); + ResultHandle lock = compute.readInstanceField(lockField, compute.getThis()); + compute.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + TryBlock tryBlock = compute.tryBlock(); + ResultHandle val = tryBlock.readInstanceField(instanceField, compute.getThis()); + BytecodeCreator isNull = tryBlock.ifNull(val).trueBranch(); + ResultHandle newVal = isNull.invokeInterfaceMethod(MethodDescriptors.SUPPLIER_GET, + compute.getMethodParam(0)); + isNull.writeInstanceField(instanceField, compute.getThis(), newVal); + tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + catchBlock.throwException(catchBlock.getCaughtException()); + compute.returnValue(compute.readInstanceField(instanceField, compute.getThis())); + + strSwitch.caseOf(bean.getIdentifier(), bc -> { + bc.returnValue(bc.invokeVirtualMethod(compute.getMethodDescriptor(), bc.getThis(), bc.getMethodParam(1))); + }); + } + strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index 57b9f53d10775..756833e572aa0 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.locks.Lock; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -307,6 +308,9 @@ public final class MethodDescriptors { public static final MethodDescriptor INTERCEPT_FUNCTION_INTERCEPT = MethodDescriptor.ofMethod(InterceptFunction.class, "intercept", Object.class, ArcInvocationContext.class); + public static final MethodDescriptor LOCK_LOCK = MethodDescriptor.ofMethod(Lock.class, "lock", void.class); + public static final MethodDescriptor LOCK_UNLOCK = MethodDescriptor.ofMethod(Lock.class, "unlock", void.class); + private MethodDescriptors() { } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ReflectionRegistration.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ReflectionRegistration.java index 828b4359039a4..ad71798242408 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ReflectionRegistration.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ReflectionRegistration.java @@ -6,6 +6,8 @@ public interface ReflectionRegistration { + void registerMethod(String declaringClass, String name, String... params); + void registerMethod(MethodInfo methodInfo); void registerField(FieldInfo fieldInfo); @@ -29,6 +31,11 @@ default void registerSubclass(DotName beanClassName, String subclassName) { } ReflectionRegistration NOOP = new ReflectionRegistration() { + + @Override + public void registerMethod(String declaringClass, String name, String... params) { + } + @Override public void registerMethod(MethodInfo methodInfo) { } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Arc.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Arc.java index 5a8d9d20afbb6..349f5793aad73 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Arc.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Arc.java @@ -24,19 +24,19 @@ public static ArcContainer initialize() { /** * - * @param arcInitConfig + * @param config * @return the container instance * @see #initialize() */ - public static ArcContainer initialize(ArcInitConfig arcInitConfig) { + public static ArcContainer initialize(ArcInitConfig config) { ArcContainerImpl container = INSTANCE.get(); if (container == null) { synchronized (INSTANCE) { container = INSTANCE.get(); if (container == null) { // Set the container instance first because Arc.container() can be used within ArcContainerImpl.init() - container = new ArcContainerImpl(arcInitConfig.getCurrentContextFactory(), - arcInitConfig.isStrictCompatibility()); + container = new ArcContainerImpl(config.getCurrentContextFactory(), + config.isStrictCompatibility(), config.isOptimizeContexts()); INSTANCE.set(container); container.init(); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInitConfig.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInitConfig.java index 596b0535a0230..976bf4b0d08d1 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInitConfig.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInitConfig.java @@ -24,10 +24,12 @@ public static Builder builder() { private ArcInitConfig(Builder builder) { this.currentContextFactory = builder.currentContextFactory; this.strictCompatibility = builder.strictCompatibility; + this.optimizeContexts = builder.optimizeContexts; } private final boolean strictCompatibility; private final CurrentContextFactory currentContextFactory; + private final boolean optimizeContexts; public boolean isStrictCompatibility() { return strictCompatibility; @@ -37,14 +39,20 @@ public CurrentContextFactory getCurrentContextFactory() { return currentContextFactory; } + public boolean isOptimizeContexts() { + return optimizeContexts; + } + public static class Builder { private boolean strictCompatibility; private CurrentContextFactory currentContextFactory; + private boolean optimizeContexts; private Builder() { // init all values with their defaults this.strictCompatibility = false; this.currentContextFactory = null; + this.optimizeContexts = false; } public Builder setStrictCompatibility(boolean strictCompatibility) { @@ -57,6 +65,11 @@ public Builder setCurrentContextFactory(CurrentContextFactory currentContextFact return this; } + public Builder setOptimizeContexts(boolean value) { + optimizeContexts = value; + return this; + } + public ArcInitConfig build() { return new ArcInitConfig(this); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java index 7bd74871c1138..8a12602f17ef5 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java @@ -6,6 +6,8 @@ import java.util.Set; import java.util.function.Supplier; +import io.quarkus.arc.impl.ContextInstances; + public final class Components { private final Collection> beans; @@ -16,13 +18,15 @@ public final class Components { private final Map, Set> transitiveInterceptorBindings; private final Map> qualifierNonbindingMembers; private final Set qualifiers; + private final Map, Supplier> contextInstances; public Components(Collection> beans, Collection> observers, Collection contexts, Set interceptorBindings, Map, Set> transitiveInterceptorBindings, Supplier> removedBeans, Map> qualifierNonbindingMembers, - Set qualifiers) { + Set qualifiers, + Map, Supplier> contextInstances) { this.beans = beans; this.observers = observers; this.contexts = contexts; @@ -31,6 +35,7 @@ public Components(Collection> beans, Collection> getBeans() { @@ -75,4 +80,21 @@ public Set getQualifiers() { return qualifiers; } + public Map, Supplier> getContextInstances() { + return contextInstances; + } + + public static Supplier newContextInstancesSupplier(Class clazz) { + return new Supplier() { + + @Override + public ContextInstances get() { + try { + return clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Unable to instantiate " + clazz, e); + } + } + }; + } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java index ab5f585af6523..748fae45d3fb7 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractSharedContext.java @@ -16,10 +16,14 @@ abstract class AbstractSharedContext implements InjectableContext, InjectableContext.ContextState { - protected final ComputingCache> instances; + protected final ContextInstances instances; public AbstractSharedContext() { - this.instances = new ComputingCache<>(); + this(new ComputingCacheContextInstances()); + } + + public AbstractSharedContext(ContextInstances instances) { + this.instances = Objects.requireNonNull(instances); } @SuppressWarnings("unchecked") @@ -47,7 +51,7 @@ public T get(Contextual contextual) { if (!Scopes.scopeMatches(this, bean)) { throw Scopes.scopeDoesNotMatchException(this, bean); } - ContextInstanceHandle handle = instances.getValueIfPresent(bean.getIdentifier()); + ContextInstanceHandle handle = instances.getIfPresent(bean.getIdentifier()); return handle != null ? (T) handle.get() : null; } @@ -63,7 +67,7 @@ public ContextState getStateIfActive() { @Override public Map, Object> getContextualInstances() { - return instances.getPresentValues().stream() + return instances.getAllPresent().stream() .collect(Collectors.toUnmodifiableMap(ContextInstanceHandle::getBean, ContextInstanceHandle::get)); } @@ -83,7 +87,7 @@ public void destroy(Contextual contextual) { @Override public synchronized void destroy() { - Set> values = instances.getPresentValues(); + Set> values = instances.getAllPresent(); // Destroy the producers first for (Iterator> iterator = values.iterator(); iterator.hasNext();) { ContextInstanceHandle instanceHandle = iterator.next(); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ApplicationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ApplicationContext.java index 2bc60e6abc04d..88ca9a41bbb15 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ApplicationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ApplicationContext.java @@ -6,6 +6,14 @@ class ApplicationContext extends AbstractSharedContext { + ApplicationContext() { + super(); + } + + ApplicationContext(ContextInstances instances) { + super(instances); + } + @Override public Class getScope() { return ApplicationScoped.class; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index a01607c33fc96..a64c0674a37cb 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -103,7 +103,7 @@ public class ArcContainerImpl implements ArcContainer { private final boolean strictMode; - public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean strictMode) { + public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean strictMode, boolean optimizeContexts) { this.strictMode = strictMode; id = String.valueOf(ID_GENERATOR.incrementAndGet()); running = new AtomicBoolean(true); @@ -117,6 +117,8 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str Map, Set> transitiveInterceptorBindings = new HashMap<>(); Map> qualifierNonbindingMembers = new HashMap<>(); Set qualifiers = new HashSet<>(); + Supplier applicationContextInstances = null; + Supplier requestContextInstances = null; this.currentContextFactory = currentContextFactory == null ? new ThreadLocalCurrentContextFactory() : currentContextFactory; @@ -142,6 +144,12 @@ public ArcContainerImpl(CurrentContextFactory currentContextFactory, boolean str transitiveInterceptorBindings.putAll(c.getTransitiveInterceptorBindings()); qualifierNonbindingMembers.putAll(c.getQualifierNonbindingMembers()); qualifiers.addAll(c.getQualifiers()); + if (applicationContextInstances == null) { + applicationContextInstances = c.getContextInstances().get(ApplicationScoped.class); + } + if (requestContextInstances == null) { + requestContextInstances = c.getContextInstances().get(RequestScoped.class); + } } // register built-in beans @@ -190,12 +198,18 @@ public List get() { this.registeredQualifiers = new Qualifiers(qualifiers, qualifierNonbindingMembers); this.registeredInterceptorBindings = new InterceptorBindings(interceptorBindings, transitiveInterceptorBindings); + ApplicationContext applicationContext = applicationContextInstances != null + ? new ApplicationContext(applicationContextInstances.get()) + : new ApplicationContext(); + RequestContext requestContext = new RequestContext(this.currentContextFactory.create(RequestScoped.class), + notifierOrNull(Set.of(Initialized.Literal.REQUEST, Any.Literal.INSTANCE)), + notifierOrNull(Set.of(BeforeDestroyed.Literal.REQUEST, Any.Literal.INSTANCE)), + notifierOrNull(Set.of(Destroyed.Literal.REQUEST, Any.Literal.INSTANCE)), + requestContextInstances != null ? requestContextInstances : ComputingCacheContextInstances::new); + Contexts.Builder contextsBuilder = new Contexts.Builder( - new RequestContext(this.currentContextFactory.create(RequestScoped.class), - notifierOrNull(Set.of(Initialized.Literal.REQUEST, Any.Literal.INSTANCE)), - notifierOrNull(Set.of(BeforeDestroyed.Literal.REQUEST, Any.Literal.INSTANCE)), - notifierOrNull(Set.of(Destroyed.Literal.REQUEST, Any.Literal.INSTANCE))), - new ApplicationContext(), + requestContext, + applicationContext, new SingletonContext(), new DependentContext()); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java new file mode 100644 index 0000000000000..fd89543cbc65b --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ComputingCacheContextInstances.java @@ -0,0 +1,41 @@ +package io.quarkus.arc.impl; + +import java.util.Set; +import java.util.function.Supplier; + +import io.quarkus.arc.ContextInstanceHandle; + +class ComputingCacheContextInstances implements ContextInstances { + + protected final ComputingCache> instances; + + ComputingCacheContextInstances() { + instances = new ComputingCache<>(); + } + + @Override + public ContextInstanceHandle computeIfAbsent(String id, Supplier> supplier) { + return instances.computeIfAbsent(id, supplier); + } + + @Override + public ContextInstanceHandle getIfPresent(String id) { + return instances.getValueIfPresent(id); + } + + @Override + public ContextInstanceHandle remove(String id) { + return instances.remove(id); + } + + @Override + public Set> getAllPresent() { + return instances.getPresentValues(); + } + + @Override + public void clear() { + instances.clear(); + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java new file mode 100644 index 0000000000000..76ca4ad531e6f --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstances.java @@ -0,0 +1,20 @@ +package io.quarkus.arc.impl; + +import java.util.Set; +import java.util.function.Supplier; + +import io.quarkus.arc.ContextInstanceHandle; + +public interface ContextInstances { + + ContextInstanceHandle computeIfAbsent(String id, Supplier> supplier); + + ContextInstanceHandle getIfPresent(String id); + + ContextInstanceHandle remove(String id); + + Set> getAllPresent(); + + void clear(); + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java index e9296569b2643..08e2043ff9179 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/RequestContext.java @@ -6,9 +6,8 @@ import java.util.Arrays; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import jakarta.enterprise.context.ContextNotActiveException; @@ -38,13 +37,16 @@ class RequestContext implements ManagedContext { private final Notifier initializedNotifier; private final Notifier beforeDestroyedNotifier; private final Notifier destroyedNotifier; + private final Supplier contextInstances; public RequestContext(CurrentContext currentContext, Notifier initializedNotifier, - Notifier beforeDestroyedNotifier, Notifier destroyedNotifier) { + Notifier beforeDestroyedNotifier, Notifier destroyedNotifier, + Supplier contextInstances) { this.currentContext = currentContext; this.initializedNotifier = initializedNotifier; this.beforeDestroyedNotifier = beforeDestroyedNotifier; this.destroyedNotifier = destroyedNotifier; + this.contextInstances = contextInstances; } @Override @@ -66,13 +68,17 @@ public T getIfActive(Contextual contextual, Function, Creat // Context is not active! return null; } - ContextInstanceHandle instance = (ContextInstanceHandle) ctxState.map.get(contextual); + ContextInstances contextInstances = ctxState.contextInstances; + ContextInstanceHandle instance = (ContextInstanceHandle) contextInstances.getIfPresent(bean.getIdentifier()); if (instance == null) { CreationalContext creationalContext = creationalContextFun.apply(contextual); - // Bean instance does not exist - create one if we have CreationalContext - instance = new ContextInstanceHandleImpl((InjectableBean) contextual, - contextual.create(creationalContext), creationalContext); - ctxState.map.put(contextual, instance); + return (T) contextInstances.computeIfAbsent(bean.getIdentifier(), new Supplier>() { + + @Override + public ContextInstanceHandle get() { + return new ContextInstanceHandleImpl<>(bean, contextual.create(creationalContext), creationalContext); + } + }).get(); } return instance.get(); } @@ -99,7 +105,8 @@ public T get(Contextual contextual) { if (state == null) { throw notActive(); } - ContextInstanceHandle instance = (ContextInstanceHandle) state.map.get(contextual); + ContextInstanceHandle instance = (ContextInstanceHandle) state.contextInstances + .getIfPresent(bean.getIdentifier()); return instance == null ? null : instance.get(); } @@ -115,7 +122,8 @@ public void destroy(Contextual contextual) { // Context is not active throw notActive(); } - ContextInstanceHandle instance = state.map.remove(contextual); + InjectableBean bean = (InjectableBean) contextual; + ContextInstanceHandle instance = state.contextInstances.remove(bean.getIdentifier()); if (instance != null) { instance.destroy(); } @@ -127,7 +135,7 @@ public ContextState activate(ContextState initialState) { traceActivate(initialState); } if (initialState == null) { - RequestContextState state = new RequestContextState(new ConcurrentHashMap<>()); + RequestContextState state = new RequestContextState(contextInstances.get()); currentContext.set(state); // Fire an event with qualifier @Initialized(RequestScoped.class) if there are any observers for it fireIfNotEmpty(initializedNotifier); @@ -202,12 +210,8 @@ public void destroy(ContextState state) { if (reqState.invalidate()) { // Fire an event with qualifier @BeforeDestroyed(RequestScoped.class) if there are any observers for it fireIfNotEmpty(beforeDestroyedNotifier); - Map, ContextInstanceHandle> map = ((RequestContextState) state).map; - if (!map.isEmpty()) { - //Performance: avoid an iterator on the map elements - map.forEach(this::destroyContextElement); - map.clear(); - } + reqState.contextInstances.getAllPresent().forEach(this::destroyContextElement); + reqState.contextInstances.clear(); // Fire an event with qualifier @Destroyed(RequestScoped.class) if there are any observers for it fireIfNotEmpty(destroyedNotifier); } @@ -225,7 +229,7 @@ private static void traceDestroy(ContextState state) { LOG.tracef("Destroy %s%s\n\t...", state != null ? Integer.toHexString(state.hashCode()) : "", stack); } - private void destroyContextElement(Contextual contextual, ContextInstanceHandle contextInstanceHandle) { + private void destroyContextElement(ContextInstanceHandle contextInstanceHandle) { try { contextInstanceHandle.destroy(); } catch (Exception e) { @@ -269,16 +273,16 @@ static class RequestContextState implements ContextState { } } - private final Map, ContextInstanceHandle> map; + private final ContextInstances contextInstances; private volatile int isValid; - RequestContextState(ConcurrentMap, ContextInstanceHandle> value) { - this.map = Objects.requireNonNull(value); + RequestContextState(ContextInstances contextInstances) { + this.contextInstances = Objects.requireNonNull(contextInstances); } @Override public Map, Object> getContextualInstances() { - return map.values().stream() + return contextInstances.getAllPresent().stream() .collect(Collectors.toUnmodifiableMap(ContextInstanceHandle::getBean, ContextInstanceHandle::get)); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SingletonContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SingletonContext.java index 549ebed4de469..96487b8cf4c2a 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SingletonContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SingletonContext.java @@ -16,7 +16,7 @@ public Class getScope() { void destroyInstance(Object instance) { InstanceHandle handle = null; - for (ContextInstanceHandle contextInstance : instances.getPresentValues()) { + for (ContextInstanceHandle contextInstance : instances.getAllPresent()) { if (contextInstance.get() == instance) { handle = contextInstance; break; diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java index 05b799d33fde4..5a06041446b7a 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java @@ -92,6 +92,7 @@ public static class Builder { private AlternativePriorities alternativePriorities; private final List buildCompatibleExtensions; private boolean strictCompatibility = false; + private boolean optimizeContexts = false; public Builder() { resourceReferenceProviders = new ArrayList<>(); @@ -213,6 +214,11 @@ public Builder strictCompatibility(boolean strictCompatibility) { return this; } + public Builder optimizeContexts(boolean value) { + this.optimizeContexts = value; + return this; + } + public ArcTestContainer build() { return new ArcTestContainer(this); } @@ -248,6 +254,7 @@ public ArcTestContainer build() { private final List buildCompatibleExtensions; private final boolean strictCompatibility; + private final boolean optimizeContexts; public ArcTestContainer(Class... beanClasses) { this.resourceReferenceProviders = Collections.emptyList(); @@ -271,6 +278,7 @@ public ArcTestContainer(Class... beanClasses) { this.alternativePriorities = null; this.buildCompatibleExtensions = Collections.emptyList(); this.strictCompatibility = false; + this.optimizeContexts = false; } public ArcTestContainer(Builder builder) { @@ -295,6 +303,7 @@ public ArcTestContainer(Builder builder) { this.alternativePriorities = builder.alternativePriorities; this.buildCompatibleExtensions = builder.buildCompatibleExtensions; this.strictCompatibility = builder.strictCompatibility; + this.optimizeContexts = builder.optimizeContexts; } // this is where we start Arc, we operate on a per-method basis @@ -403,14 +412,16 @@ private ClassLoader init(ExtensionContext context) { } } + String deploymentName = testClass.getName().replace('.', '_'); BeanProcessor.Builder builder = BeanProcessor.builder() - .setName(testClass.getName().replace('.', '_')) + .setName(deploymentName) .setImmutableBeanArchiveIndex(immutableBeanArchiveIndex) .setComputingBeanArchiveIndex(BeanArchives.buildComputingBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), immutableBeanArchiveIndex)) .setApplicationIndex(applicationIndex) .setBuildCompatibleExtensions(buildCompatibleExtensions) - .setStrictCompatibility(strictCompatibility); + .setStrictCompatibility(strictCompatibility) + .setOptimizeContexts(optimizeContexts); if (!resourceAnnotations.isEmpty()) { builder.addResourceAnnotations(resourceAnnotations.stream() .map(c -> DotName.createSimple(c.getName())) @@ -469,7 +480,10 @@ public void writeResource(Resource resource) throws IOException { .setContextClassLoader(testClassLoader); // Now we are ready to initialize Arc - Arc.initialize(ArcInitConfig.builder().setStrictCompatibility(strictCompatibility).build()); + ArcInitConfig.Builder initConfigBuilder = ArcInitConfig.builder(); + initConfigBuilder.setStrictCompatibility(strictCompatibility); + initConfigBuilder.setOptimizeContexts(optimizeContexts); + Arc.initialize(initConfigBuilder.build()); } catch (Throwable e) { if (shouldFail) { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/optimized/ApplicationContextInstancesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/optimized/ApplicationContextInstancesTest.java new file mode 100644 index 0000000000000..247173814784e --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/optimized/ApplicationContextInstancesTest.java @@ -0,0 +1,61 @@ +package io.quarkus.arc.test.contexts.application.optimized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.UUID; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InjectableContext; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; + +public class ApplicationContextInstancesTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Boom.class) + .optimizeContexts(true) + .build(); + + @Test + public void testContext() { + ArcContainer container = Arc.container(); + InstanceHandle handle = container.instance(Boom.class); + Boom boom = handle.get(); + String id1 = boom.ping(); + assertEquals(id1, boom.ping()); + + handle.destroy(); + String id2 = boom.ping(); + assertNotEquals(id1, id2); + assertEquals(id2, boom.ping()); + + InjectableContext appContext = container.getActiveContext(ApplicationScoped.class); + appContext.destroy(); + assertNotEquals(id2, boom.ping()); + } + + @ApplicationScoped + public static class Boom { + + private String id; + + String ping() { + return id; + } + + @PostConstruct + void init() { + id = UUID.randomUUID().toString(); + } + + } +}