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 index 01a7ba04fc80a..24944d995e807 100644 --- 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 @@ -5,6 +5,7 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_VOLATILE; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -13,6 +14,7 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; import java.util.function.Supplier; import org.jboss.jandex.DotName; @@ -33,7 +35,7 @@ public class ContextInstancesGenerator extends AbstractGenerator { - static final String APP_CONTEXT_INSTANCES_SUFFIX = "_ContextInstances"; + static final String CONTEXT_INSTANCES_SUFFIX = "_ContextInstances"; private final BeanDeployment beanDeployment; private final Map scopeToGeneratedName; @@ -48,7 +50,7 @@ public ContextInstancesGenerator(boolean generateSources, ReflectionRegistration void precomputeGeneratedName(DotName scope) { String generatedName = DEFAULT_PACKAGE + "." + beanDeployment.name + UNDERSCORE + scope.toString().replace(".", UNDERSCORE) - + APP_CONTEXT_INSTANCES_SUFFIX; + + CONTEXT_INSTANCES_SUFFIX; scopeToGeneratedName.put(scope, generatedName); } @@ -61,96 +63,127 @@ Collection generate(DotName scope) { ClassCreator contextInstances = ClassCreator.builder().classOutput(classOutput).className(generatedName) .interfaces(ContextInstances.class).build(); - // Add fields for all beans + // Add ContextInstanceHandle and Lock fields for every bean // The name of the field is a generated index // For example: // private volatile ContextInstanceHandle 1; - Map idToField = new HashMap<>(); + // private final Lock 1l = new ReentrantLock(); + Map idToFields = new HashMap<>(); int fieldIndex = 0; for (BeanInfo bean : beans) { - FieldCreator fc = contextInstances.getFieldCreator("" + fieldIndex++, ContextInstanceHandle.class) + String beanIdx = "" + fieldIndex++; + FieldCreator handleField = contextInstances.getFieldCreator(beanIdx, ContextInstanceHandle.class) .setModifiers(ACC_PRIVATE | ACC_VOLATILE); - idToField.put(bean.getIdentifier(), fc.getFieldDescriptor()); + FieldCreator lockField = contextInstances.getFieldCreator(beanIdx + "l", Lock.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + idToFields.put(bean.getIdentifier(), + new InstanceAndLock(handleField.getFieldDescriptor(), lockField.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))); + for (InstanceAndLock fields : idToFields.values()) { + constructor.writeInstanceField(fields.lock, 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()); + implementComputeIfAbsent(contextInstances, beans, idToFields); + implementGetIfPresent(contextInstances, beans, idToFields); + implementRemove(contextInstances, beans, idToFields); + implementGetAllPresent(contextInstances, idToFields); + implementRemoveEach(contextInstances, idToFields); + + // These methods are needed to significantly reduce the size of the stack map table for getAllPresent() and removeEach() + implementLockAll(contextInstances, idToFields); + implementUnlockAll(contextInstances, idToFields); contextInstances.close(); return classOutput.getResources(); } - private void implementGetAllPresent(ClassCreator contextInstances, Map idToField, - FieldDescriptor lockField) { + private void implementGetAllPresent(ClassCreator contextInstances, Map idToFields) { 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; + // this.lockAll(); + // ContextInstanceHandle copy1 = this.1; + // this.unlockAll(); + // Set> ret = new HashSet<>(); + // if (copy1 != null) { + // ret.add(copy1); // } - 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); + // return ret; + getAllPresent.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "lockAll", void.class), + getAllPresent.getThis()); + List results = new ArrayList<>(idToFields.size()); + for (InstanceAndLock fields : idToFields.values()) { + results.add(getAllPresent.readInstanceField(fields.instance, getAllPresent.getThis())); + } + getAllPresent.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "unlockAll", void.class), + getAllPresent.getThis()); + ResultHandle ret = getAllPresent.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (ResultHandle result : results) { + getAllPresent.ifNotNull(result).trueBranch().invokeInterfaceMethod(MethodDescriptors.SET_ADD, ret, result); + } + getAllPresent.returnValue(ret); + } + + private void implementLockAll(ClassCreator contextInstances, Map idToFields) { + MethodCreator lockAll = contextInstances.getMethodCreator("lockAll", void.class) + .setModifiers(ACC_PRIVATE); + for (InstanceAndLock fields : idToFields.values()) { + ResultHandle lock = lockAll.readInstanceField(fields.lock, lockAll.getThis()); + lockAll.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + } + lockAll.returnVoid(); + } + + private void implementUnlockAll(ClassCreator contextInstances, Map idToFields) { + MethodCreator unlockAll = contextInstances.getMethodCreator("unlockAll", void.class) + .setModifiers(ACC_PRIVATE); + for (InstanceAndLock fields : idToFields.values()) { + ResultHandle lock = unlockAll.readInstanceField(fields.lock, unlockAll.getThis()); + unlockAll.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); } - 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()); + unlockAll.returnVoid(); } - private void implementClear(ClassCreator applicationContextInstances, Map idToField, - FieldDescriptor lockField) { - MethodCreator clear = applicationContextInstances.getMethodCreator("clear", void.class).setModifiers(ACC_PUBLIC); - // lock.lock(); - // try { + private void implementRemoveEach(ClassCreator contextInstances, Map idToFields) { + MethodCreator removeEach = contextInstances.getMethodCreator("removeEach", void.class, Consumer.class) + .setModifiers(ACC_PUBLIC); + // this.lockAll(); + // ContextInstanceHandle copy1 = this.1; + // if (copy1 != null) { // 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()); + // this.unlockAll(); + // if (action != null) + // if (copy1 != null) { + // consumer.accept(copy1); + // } + // } + removeEach.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "lockAll", void.class), + removeEach.getThis()); + List results = new ArrayList<>(idToFields.size()); + for (InstanceAndLock fields : idToFields.values()) { + ResultHandle copy = removeEach.readInstanceField(fields.instance, removeEach.getThis()); + results.add(copy); + BytecodeCreator isNotNull = removeEach.ifNotNull(copy).trueBranch(); + isNotNull.writeInstanceField(fields.instance, isNotNull.getThis(), isNotNull.loadNull()); + } + removeEach.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "unlockAll", void.class), + removeEach.getThis()); + BytecodeCreator actionIsNotNull = removeEach.ifNotNull(removeEach.getMethodParam(0)).trueBranch(); + for (ResultHandle result : results) { + BytecodeCreator isNotNull = actionIsNotNull.ifNotNull(result).trueBranch(); + isNotNull.invokeInterfaceMethod(MethodDescriptors.CONSUMER_ACCEPT, removeEach.getMethodParam(0), result); } - tryBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - tryBlock.returnVoid(); - CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); - catchBlock.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - catchBlock.throwException(catchBlock.getCaughtException()); + removeEach.returnVoid(); } - private void implementRemove(ClassCreator contextInstances, List applicationScopedBeans, - Map idToField, FieldDescriptor lockField) { + private void implementRemove(ClassCreator contextInstances, List beans, + Map idToFields) { MethodCreator remove = contextInstances .getMethodCreator("remove", ContextInstanceHandle.class, String.class) .setModifiers(ACC_PUBLIC); @@ -158,45 +191,37 @@ private void implementRemove(ClassCreator contextInstances, List appli StringSwitch strSwitch = remove.stringSwitch(remove.getMethodParam(0)); // https://github.com/quarkusio/gizmo/issues/164 strSwitch.fallThrough(); - for (BeanInfo bean : applicationScopedBeans) { - FieldDescriptor instanceField = idToField.get(bean.getIdentifier()); - // There is a separate remove method for every bean instance field - MethodCreator removeBean = contextInstances.getMethodCreator("r" + instanceField.getName(), + for (BeanInfo bean : beans) { + InstanceAndLock fields = idToFields.get(bean.getIdentifier()); + FieldDescriptor instanceField = fields.instance; + // There is a separate remove method for every instance handle field + // To eliminate large stack map table in the bytecode + MethodCreator removeHandle = contextInstances.getMethodCreator("r" + instanceField.getName(), ContextInstanceHandle.class).setModifiers(ACC_PRIVATE); - // lock.lock(); - // try { - // ContextInstanceHandle copy = this.1; - // if (copy != null) { - // this.1 = null; - // } - // lock.unlock(); - // return copy; - // } catch(Throwable t) { - // lock.unlock(); - // throw t; + // this.1l.lock(); + // ContextInstanceHandle copy = this.1; + // if (copy != null) { + // this.1 = null; // } - - 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(); + // this.1l.unlock(); + // return copy; + ResultHandle lock = removeHandle.readInstanceField(fields.lock, removeHandle.getThis()); + removeHandle.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + ResultHandle copy = removeHandle.readInstanceField(instanceField, removeHandle.getThis()); + BytecodeCreator isNotNull = removeHandle.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()); + removeHandle.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); + removeHandle.returnValue(copy); strSwitch.caseOf(bean.getIdentifier(), bc -> { - bc.returnValue(bc.invokeVirtualMethod(removeBean.getMethodDescriptor(), bc.getThis())); + bc.returnValue(bc.invokeVirtualMethod(removeHandle.getMethodDescriptor(), bc.getThis())); }); } strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); } - private void implementGetIfPresent(ClassCreator contextInstances, List applicationScopedBeans, - Map idToField) { + private void implementGetIfPresent(ClassCreator contextInstances, List beans, + Map idToFields) { MethodCreator getIfPresent = contextInstances .getMethodCreator("getIfPresent", ContextInstanceHandle.class, String.class) .setModifiers(ACC_PUBLIC); @@ -204,16 +229,16 @@ private void implementGetIfPresent(ClassCreator contextInstances, List StringSwitch strSwitch = getIfPresent.stringSwitch(getIfPresent.getMethodParam(0)); // https://github.com/quarkusio/gizmo/issues/164 strSwitch.fallThrough(); - for (BeanInfo bean : applicationScopedBeans) { + for (BeanInfo bean : beans) { strSwitch.caseOf(bean.getIdentifier(), bc -> { - bc.returnValue(bc.readInstanceField(idToField.get(bean.getIdentifier()), bc.getThis())); + bc.returnValue(bc.readInstanceField(idToFields.get(bean.getIdentifier()).instance, bc.getThis())); }); } strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); } - private void implementComputeIfAbsent(ClassCreator contextInstances, List applicationScopedBeans, - Map idToField, FieldDescriptor lockField) { + private void implementComputeIfAbsent(ClassCreator contextInstances, List beans, + Map idToFields) { MethodCreator computeIfAbsent = contextInstances .getMethodCreator("computeIfAbsent", ContextInstanceHandle.class, String.class, Supplier.class) .setModifiers(ACC_PUBLIC); @@ -221,41 +246,41 @@ private void implementComputeIfAbsent(ClassCreator contextInstances, List copy = this.1; // if (copy != null) { // return copy; // } - // lock.lock(); + // this.1l.lock(); // try { // if (this.1 == null) { // this.1 = supplier.get(); // } - // lock.unlock(); + // this.1l.unlock(); // return this.1; // } catch(Throwable t) { - // lock.unlock(); + // this.1l.unlock(); // throw t; // } - ResultHandle copy = compute.readInstanceField(instanceField, compute.getThis()); + ResultHandle copy = compute.readInstanceField(fields.instance, compute.getThis()); compute.ifNotNull(copy).trueBranch().returnValue(copy); - ResultHandle lock = compute.readInstanceField(lockField, compute.getThis()); + ResultHandle lock = compute.readInstanceField(fields.lock, compute.getThis()); compute.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); TryBlock tryBlock = compute.tryBlock(); - ResultHandle val = tryBlock.readInstanceField(instanceField, compute.getThis()); + ResultHandle val = tryBlock.readInstanceField(fields.instance, compute.getThis()); BytecodeCreator isNull = tryBlock.ifNull(val).trueBranch(); ResultHandle newVal = isNull.invokeInterfaceMethod(MethodDescriptors.SUPPLIER_GET, compute.getMethodParam(0)); - isNull.writeInstanceField(instanceField, compute.getThis(), newVal); + isNull.writeInstanceField(fields.instance, 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())); + compute.returnValue(compute.readInstanceField(fields.instance, compute.getThis())); strSwitch.caseOf(bean.getIdentifier(), bc -> { bc.returnValue(bc.invokeVirtualMethod(compute.getMethodDescriptor(), bc.getThis(), bc.getMethodParam(1))); @@ -264,4 +289,23 @@ private void implementComputeIfAbsent(ClassCreator contextInstances, List bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); } + final class InstanceAndLock { + + private final FieldDescriptor instance, lock; + + InstanceAndLock(FieldDescriptor instance, FieldDescriptor lock) { + this.instance = instance; + this.lock = lock; + } + + FieldDescriptor instance() { + return instance; + } + + FieldDescriptor lock() { + return lock; + } + + } + } 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 8cc52eb815fb9..79b2f7af860b2 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 @@ -11,6 +11,7 @@ import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -63,6 +64,9 @@ public final class MethodDescriptors { public static final MethodDescriptor SUPPLIER_GET = MethodDescriptor.ofMethod(Supplier.class, "get", Object.class); + public static final MethodDescriptor CONSUMER_ACCEPT = MethodDescriptor.ofMethod(Consumer.class, "accept", + void.class, Object.class); + public static final MethodDescriptor CREATIONAL_CTX_CHILD = MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", CreationalContextImpl.class, CreationalContext.class); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java index 55cc81bed93f8..e9e04ef605da8 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInstanceHandle.java @@ -7,8 +7,6 @@ import jakarta.enterprise.context.Dependent; import jakarta.enterprise.context.spi.CreationalContext; -import org.jboss.logging.Logger; - import io.quarkus.arc.Arc; import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableContext; @@ -16,8 +14,6 @@ abstract class AbstractInstanceHandle implements InstanceHandle { - private static final Logger LOGGER = Logger.getLogger(AbstractInstanceHandle.class.getName()); - @SuppressWarnings("rawtypes") private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER = AtomicIntegerFieldUpdater .newUpdater(AbstractInstanceHandle.class, "destroyed"); 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 748fae45d3fb7..3648dfce08890 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 @@ -1,9 +1,6 @@ package io.quarkus.arc.impl; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -87,19 +84,24 @@ public void destroy(Contextual contextual) { @Override public synchronized void destroy() { + // Note that shared contexts are usually only destroyed when the app stops + // I.e. we don't need to use the optimized ContextInstances methods here Set> values = instances.getAllPresent(); + if (values.isEmpty()) { + return; + } // Destroy the producers first - for (Iterator> iterator = values.iterator(); iterator.hasNext();) { - ContextInstanceHandle instanceHandle = iterator.next(); + for (Iterator> it = values.iterator(); it.hasNext();) { + ContextInstanceHandle instanceHandle = it.next(); if (instanceHandle.getBean().getDeclaringBean() != null) { instanceHandle.destroy(); - iterator.remove(); + it.remove(); } } for (ContextInstanceHandle instanceHandle : values) { instanceHandle.destroy(); } - instances.clear(); + instances.removeEach(null); } @Override 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 index fd89543cbc65b..a875191831f7d 100644 --- 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 @@ -1,6 +1,7 @@ package io.quarkus.arc.impl; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; import io.quarkus.arc.ContextInstanceHandle; @@ -34,7 +35,10 @@ public Set> getAllPresent() { } @Override - public void clear() { + public void removeEach(Consumer> action) { + if (action != null) { + instances.getPresentValues().forEach(action); + } instances.clear(); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java index fcfce31cbe023..948f79e161a73 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextInstanceHandleImpl.java @@ -2,6 +2,8 @@ import jakarta.enterprise.context.spi.CreationalContext; +import org.jboss.logging.Logger; + import io.quarkus.arc.ContextInstanceHandle; import io.quarkus.arc.InjectableBean; @@ -12,13 +14,19 @@ */ public class ContextInstanceHandleImpl extends EagerInstanceHandle implements ContextInstanceHandle { + private static final Logger LOG = Logger.getLogger(ContextInstanceHandleImpl.class); + public ContextInstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext) { super(bean, instance, creationalContext); } @Override public void destroy() { - destroyInternal(); + try { + destroyInternal(); + } catch (Exception e) { + LOG.error("Unable to destroy instance" + get(), e); + } } } 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 index 76ca4ad531e6f..bf3d24c39a3cd 100644 --- 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 @@ -1,20 +1,46 @@ package io.quarkus.arc.impl; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; import io.quarkus.arc.ContextInstanceHandle; public interface ContextInstances { + /** + * + * @param id + * @param supplier + * @return the instance handle + */ ContextInstanceHandle computeIfAbsent(String id, Supplier> supplier); + /** + * + * @param id + * @return the instance handle if present, {@code null} otherwise + */ ContextInstanceHandle getIfPresent(String id); + /** + * + * @param id + * @return the removed instance handle, or {@code null} + */ ContextInstanceHandle remove(String id); + /** + * + * @return all instance handles + */ Set> getAllPresent(); - void clear(); + /** + * Removes all instance handles and performs the given action (if present) for each handle. + * + * @param action + */ + void removeEach(Consumer> action); } 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 08e2043ff9179..762663007603f 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 @@ -210,8 +210,7 @@ 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); - reqState.contextInstances.getAllPresent().forEach(this::destroyContextElement); - reqState.contextInstances.clear(); + reqState.contextInstances.removeEach(ContextInstanceHandle::destroy); // Fire an event with qualifier @Destroyed(RequestScoped.class) if there are any observers for it fireIfNotEmpty(destroyedNotifier); } @@ -229,14 +228,6 @@ private static void traceDestroy(ContextState state) { LOG.tracef("Destroy %s%s\n\t...", state != null ? Integer.toHexString(state.hashCode()) : "", stack); } - private void destroyContextElement(ContextInstanceHandle contextInstanceHandle) { - try { - contextInstanceHandle.destroy(); - } catch (Exception e) { - throw new IllegalStateException("Unable to destroy instance" + contextInstanceHandle.get(), e); - } - } - private void fireIfNotEmpty(Notifier notifier) { if (notifier != null && !notifier.isEmpty()) { try { 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 index 247173814784e..69695d80be209 100644 --- 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 @@ -4,9 +4,17 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -21,7 +29,7 @@ public class ApplicationContextInstancesTest { @RegisterExtension ArcTestContainer container = ArcTestContainer.builder() - .beanClasses(Boom.class) + .beanClasses(Boom.class, Bim.class) .optimizeContexts(true) .build(); @@ -30,15 +38,23 @@ public void testContext() { ArcContainer container = Arc.container(); InstanceHandle handle = container.instance(Boom.class); Boom boom = handle.get(); + // ContextInstances#computeIfAbsent() String id1 = boom.ping(); assertEquals(id1, boom.ping()); + // ContextInstances#remove() handle.destroy(); + // Bim bean is not destroyed + // ContextInstances#getAllPresent() + assertEquals(1, container.getActiveContext(ApplicationScoped.class).getState().getContextualInstances().size()); + + // Init a new instance of Boom String id2 = boom.ping(); assertNotEquals(id1, id2); assertEquals(id2, boom.ping()); InjectableContext appContext = container.getActiveContext(ApplicationScoped.class); + // ContextInstances#removeEach() appContext.destroy(); assertNotEquals(id2, boom.ping()); } @@ -48,6 +64,9 @@ public static class Boom { private String id; + @Inject + Bim bim; + String ping() { return id; } @@ -55,6 +74,31 @@ String ping() { @PostConstruct void init() { id = UUID.randomUUID().toString(); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future f = executorService.submit(() -> { + // Force the init of the bean on a different thread + bim.bam(); + }); + try { + f.get(2, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IllegalStateException(e); + } + executorService.shutdownNow(); + } + + @PreDestroy + void destroy() { + throw new IllegalStateException("Boom"); + } + + } + + @ApplicationScoped + public static class Bim { + + public void bam() { } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/request/optimized/RequestContextInstancesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/request/optimized/RequestContextInstancesTest.java new file mode 100644 index 0000000000000..6ceaa360d3908 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/request/optimized/RequestContextInstancesTest.java @@ -0,0 +1,109 @@ +package io.quarkus.arc.test.contexts.request.optimized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; + +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 RequestContextInstancesTest { + + @RegisterExtension + ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Boom.class, Bim.class) + .optimizeContexts(true) + .build(); + + @Test + public void testContext() { + ArcContainer container = Arc.container(); + container.requestContext().activate(); + + InstanceHandle handle = container.instance(Boom.class); + Boom boom = handle.get(); + // ContextInstances#computeIfAbsent() + String id1 = boom.ping(); + assertEquals(id1, boom.ping()); + + // ContextInstances#remove() + handle.destroy(); + // ContextInstances#getAllPresent() + assertEquals(0, container.getActiveContext(RequestScoped.class).getState().getContextualInstances().size()); + + // Init a new instance of Boom + String id2 = boom.ping(); + assertNotEquals(id1, id2); + assertEquals(id2, boom.ping()); + + InjectableContext appContext = container.getActiveContext(RequestScoped.class); + // ContextInstances#removeEach() + appContext.destroy(); + assertNotEquals(id2, boom.ping()); + + container.requestContext().terminate(); + } + + @RequestScoped + public static class Boom { + + private String id; + + @Inject + Bim bim; + + String ping() { + return id; + } + + @PostConstruct + void init() { + id = UUID.randomUUID().toString(); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future f = executorService.submit(() -> { + // Force the init of the bean on a different thread + bim.bam(); + }); + try { + f.get(2, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IllegalStateException(e); + } + executorService.shutdownNow(); + } + + @PreDestroy + void destroy() { + throw new IllegalStateException("Boom"); + } + + } + + @ApplicationScoped + public static class Bim { + + public void bam() { + } + + } +}