Skip to content

Commit

Permalink
Request scope performance improvements
Browse files Browse the repository at this point in the history
Previously this required 3 ThreadLocal operations:

- isActive()
- create(Contextual)
- create(Contextual, CreationalContext)

This PR provides a single operation so that these can
be accomplished with only a single ThreadLocal op
  • Loading branch information
stuartwdouglas committed Oct 16, 2020
1 parent e3a4bf9 commit b972eb4
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_VOLATILE;

import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.ClientProxy;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableContext;
import io.quarkus.arc.impl.CreationalContextImpl;
import io.quarkus.arc.impl.Mockable;
import io.quarkus.arc.processor.BeanGenerator.ProviderType;
import io.quarkus.arc.processor.ResourceOutput.Resource;
import io.quarkus.gizmo.AssignableResultHandle;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.DescriptorUtils;
Expand All @@ -32,7 +31,6 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.spi.Contextual;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
Expand Down Expand Up @@ -284,33 +282,17 @@ void implementDelegate(ClassCreator clientProxy, ProviderType providerType, Fiel
// Application context stored in a field and is always active
contextHandle = creator.readInstanceField(
FieldDescriptor.of(clientProxy.getClassName(), CONTEXT_FIELD, InjectableContext.class), creator.getThis());

creator.returnValue(creator.invokeInterfaceMethod(
MethodDescriptor.ofMethod(InjectableContext.class, "getOrCreate", Object.class, Contextual.class),
contextHandle, beanHandle));
} else {
// Arc.container()
ResultHandle container = creator.invokeStaticMethod(MethodDescriptors.ARC_CONTAINER);
// bean.getScope()
ResultHandle scope = creator
.invokeInterfaceMethod(MethodDescriptor.ofMethod(InjectableBean.class, "getScope", Class.class),
beanHandle);
// getContext()
contextHandle = creator.invokeInterfaceMethod(MethodDescriptors.ARC_CONTAINER_GET_ACTIVE_CONTEXT,
container, scope);

BytecodeCreator inactiveBranch = creator.ifNull(contextHandle).trueBranch();
ResultHandle exception = inactiveBranch.newInstance(
MethodDescriptor.ofConstructor(ContextNotActiveException.class, String.class),
inactiveBranch.invokeVirtualMethod(MethodDescriptors.OBJECT_TO_STRING, scope));
inactiveBranch.throwException(exception);
creator.returnValue(creator.invokeInterfaceMethod(
MethodDescriptor.ofMethod(ArcContainer.class, "normalScopedInstance", Object.class, InjectableBean.class),
container, beanHandle));
}

AssignableResultHandle ret = creator.createVariable(Object.class);
creator.assign(ret, creator.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET_IF_PRESENT, contextHandle, beanHandle));
BytecodeCreator isNullBranch = creator.ifNull(ret).trueBranch();
// Create a new contextual instance - new CreationalContextImpl<>()
ResultHandle creationContext = isNullBranch
.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class), beanHandle);
isNullBranch.assign(ret,
isNullBranch.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET, contextHandle, beanHandle, creationContext));
creator.returnValue(ret);
}

void implementGetContextualInstance(ClassCreator clientProxy, ProviderType providerType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ public interface ArcContainer {
*/
<T> InstanceHandle<T> instance(InjectableBean<T> bean);

/**
* Returns an instance of a normal scoped bean
*
* @param bean
* @return a new bean instance
*/
<T> T normalScopedInstance(InjectableBean<T> bean);

/**
* Instances of dependent scoped beans obtained with the returned injectable instance must be explicitly destroyed, either
* via the {@link Instance#destroy(Object)} method invoked upon the same injectable instance or with
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.quarkus.arc;

import io.quarkus.arc.impl.CreationalContextImpl;
import java.util.Map;
import javax.enterprise.context.NormalScope;
import javax.enterprise.context.spi.AlterableContext;
import javax.enterprise.context.spi.Contextual;

/**
* A context implementing this interface allows to capture and view its state via {@link ContextState}.
Expand All @@ -22,6 +24,27 @@ public interface InjectableContext extends AlterableContext {
*/
ContextState getState();

/**
* Attempts to get or create a new isntance of the given contextual. If the scope is not active this returns null.
*
* This allows for the isActive check and the actual creation to happen in a single method, which gives a performance
* benefit by performing fewer thread local operations.
*
* @param contextual The bean
* @param <T> The type of bean
* @return
*/
default <T> T getOrCreate(Contextual<T> contextual) {
if (!isActive()) {
return null;
}
T result = get(contextual);
if (result != null) {
return result;
}
return get(contextual, new CreationalContextImpl<>(contextual));
}

/**
* Destroy all contextual instances from the given state.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.BeforeDestroyed;
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.Destroyed;
import javax.enterprise.context.Initialized;
Expand Down Expand Up @@ -181,18 +182,17 @@ public InjectableContext getActiveContext(Class<? extends Annotation> scopeType)
} else if (Singleton.class.equals(scopeType)) {
return singletonContext;
}
List<InjectableContext> active = new ArrayList<>();
InjectableContext selected = null;
for (InjectableContext context : contexts) {
if (scopeType.equals(context.getScope()) && context.isActive()) {
active.add(context);
if (selected != null) {
throw new IllegalArgumentException(
"More than one context object for the given scope: " + selected + " " + context);
}
selected = context;
}
}
if (active.isEmpty()) {
return null;
} else if (active.size() == 1) {
return active.get(0);
}
throw new IllegalArgumentException("More than one context object for the given scope: " + active);
return selected;
}

@Override
Expand Down Expand Up @@ -263,6 +263,39 @@ public <T> InstanceHandle<T> instance(InjectableBean<T> bean) {
return (InstanceHandle<T>) beanInstanceHandle(bean, null);
}

@Override
public <T> T normalScopedInstance(InjectableBean<T> bean) {
requireRunning();
Class<? extends Annotation> scopeType = bean.getScope();
// Application/Singleton context is always active
if (ApplicationScoped.class.equals(scopeType)) {
return applicationContext.getOrCreate(bean);
} else if (Singleton.class.equals(scopeType)) {
return applicationContext.getOrCreate(bean);
}
T result = null;
InjectableContext selectedContext = null;
for (InjectableContext context : contexts) {
if (scopeType.equals(context.getScope())) {
if (result != null) {
if (context.isActive()) {
throw new IllegalArgumentException(
"More than one context object for the given scope: " + selectedContext + " " + context);
}
} else {
result = context.getOrCreate(bean);
if (result != null) {
selectedContext = context;
}
}
}
}
if (result == null) {
throw new ContextNotActiveException();
}
return result;
}

@Override
public <T> InjectableInstance<T> select(Class<T> type, Annotation... qualifiers) {
return instance.select(type, qualifiers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ public Class<? extends Annotation> getScope() {
return RequestScoped.class;
}

@Override
public <T> T getOrCreate(Contextual<T> contextual) {
if (contextual == null) {
throw new IllegalArgumentException("Contextual parameter must not be null");
}
Map<Contextual<?>, ContextInstanceHandle<?>> ctx = currentContext.get();
if (ctx == null) {
// Thread local not set - context is not active!
return null;
}
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) ctx.get(contextual);
if (instance == null) {
CreationalContext<T> creationalContext = new CreationalContextImpl<>(contextual);
// Bean instance does not exist - create one if we have CreationalContext
instance = new ContextInstanceHandleImpl<T>((InjectableBean<T>) contextual,
contextual.create(creationalContext), creationalContext);
ctx.put(contextual, instance);
}
return instance.get();
}

@SuppressWarnings("unchecked")
@Override
public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) {
Expand Down

0 comments on commit b972eb4

Please sign in to comment.