Skip to content

Commit

Permalink
ArC - introduce the ContextReferenceFactory abstraction
Browse files Browse the repository at this point in the history
- this API allows quarkus to supply an optimized backend for any
non-shared context of a normal scope
- currently it's only used by the request context
- an extension can provide a custom factory via
ContextReferenceFactoryBuildItem
  • Loading branch information
mkouba authored and manovotn committed Mar 2, 2022
1 parent 7ca73f3 commit 77fc6b2
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.jboss.logging.Logger;

import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.ContextReferenceFactory;
import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem;
import io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem.ContextConfiguratorBuildItem;
import io.quarkus.arc.deployment.ObserverRegistrationPhaseBuildItem.ObserverConfiguratorBuildItem;
Expand Down Expand Up @@ -102,6 +103,7 @@
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.QuarkusMain;
import io.quarkus.runtime.test.TestApplicationClassPredicate;
import io.quarkus.runtime.util.HashUtil;
Expand Down Expand Up @@ -480,7 +482,8 @@ public BeanContainerBuildItem generateResources(ArcConfig config, ArcRecorder re
LiveReloadBuildItem liveReloadBuildItem,
BuildProducer<GeneratedResourceBuildItem> generatedResource,
BuildProducer<BytecodeTransformerBuildItem> bytecodeTransformer,
List<ReflectiveBeanClassBuildItem> reflectiveBeanClasses) throws Exception {
List<ReflectiveBeanClassBuildItem> reflectiveBeanClasses,
Optional<ContextReferenceFactoryBuildItem> customContextReferenceFactory) throws Exception {

for (ValidationErrorBuildItem validationError : validationErrors) {
for (Throwable error : validationError.getValues()) {
Expand Down Expand Up @@ -561,7 +564,11 @@ public void registerSubclass(DotName beanClassName, String subclassName) {
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, binding.name().toString()));
}

ArcContainer container = recorder.getContainer(shutdown);
RuntimeValue<ContextReferenceFactory> contextReferenceFactory = null;
if (customContextReferenceFactory.isPresent()) {
contextReferenceFactory = customContextReferenceFactory.get().getFactory();
}
ArcContainer container = recorder.initContainer(shutdown, contextReferenceFactory);
BeanContainer beanContainer = recorder.initBeanContainer(container,
beanContainerListenerBuildItems.stream().map(BeanContainerListenerBuildItem::getBeanContainerListener)
.collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.arc.deployment;

import io.quarkus.arc.ContextReferenceFactory;
import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.runtime.RuntimeValue;

/**
* An extension can provide a custom {@link ContextReferenceFactory}.
*/
public final class ContextReferenceFactoryBuildItem extends SimpleBuildItem {

private final RuntimeValue<ContextReferenceFactory> factory;

public ContextReferenceFactoryBuildItem(RuntimeValue<ContextReferenceFactory> factory) {
this.factory = factory;
}

public RuntimeValue<ContextReferenceFactory> getFactory() {
return factory;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.ContextReferenceFactory;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableBean.Kind;
import io.quarkus.arc.impl.ArcContainerImpl;
Expand All @@ -36,8 +37,9 @@ public class ArcRecorder {
*/
public static volatile Map<String, Supplier<?>> supplierMap;

public ArcContainer getContainer(ShutdownContext shutdown) throws Exception {
ArcContainer container = Arc.initialize();
public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue<ContextReferenceFactory> contextReferenceFactory)
throws Exception {
ArcContainer container = Arc.initialize(contextReferenceFactory != null ? contextReferenceFactory.getValue() : null);
shutdown.addShutdownTask(new Runnable() {
@Override
public void run() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ public final class Arc {

private static final AtomicReference<ArcContainerImpl> INSTANCE = new AtomicReference<>();

public static ArcContainer initialize() {
return initialize(null);
}

/**
*
* @return the initialized container
*/
public static ArcContainer initialize() {
public static ArcContainer initialize(ContextReferenceFactory contextReferenceFactory) {
ArcContainerImpl container = INSTANCE.get();
if (container == null) {
synchronized (INSTANCE) {
container = INSTANCE.get();
if (container == null) {
container = new ArcContainerImpl();
container = new ArcContainerImpl(contextReferenceFactory);
// Set the container instance first because Arc.container() can be used within ArcContainerImpl.init()
INSTANCE.set(container);
container.init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,10 @@ public interface ArcContainer {
* @return the default executor service
*/
ExecutorService getExecutorService();

/**
*
* @return the factory
*/
ContextReferenceFactory getContextReferenceFactory();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.arc;

import io.quarkus.arc.InjectableContext.ContextState;

/**
* Represents the current context of a normal scope.
*
* @param <T>
* @see ContextReferenceFactory
*/
public interface ContextReference<T extends ContextState> {

/**
*
* @return the current state
*/
T get();

/**
* Sets the current state.
*
* @param state
*/
void set(T state);

/**
* Removes the current state.
*/
void remove();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.arc;

import io.quarkus.arc.InjectableContext.ContextState;

/**
* This factory is used to create a new {@link ContextReference} for a non-shared context of a normal scope, e.g. the request
* context.
*
* @param <T>
*/
public interface ContextReferenceFactory {

<T extends ContextState> ContextReference<T> create();

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.Components;
import io.quarkus.arc.ComponentsProvider;
import io.quarkus.arc.ContextReferenceFactory;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableContext;
import io.quarkus.arc.InjectableDecorator;
Expand Down Expand Up @@ -95,7 +96,9 @@ public class ArcContainerImpl implements ArcContainer {

private volatile ExecutorService executorService;

public ArcContainerImpl() {
private final ContextReferenceFactory contextReferenceFactory;

public ArcContainerImpl(ContextReferenceFactory contextReferenceFactory) {
id = String.valueOf(ID_GENERATOR.incrementAndGet());
running = new AtomicBoolean(true);
List<InjectableBean<?>> beans = new ArrayList<>();
Expand All @@ -105,10 +108,12 @@ public ArcContainerImpl() {
List<InjectableObserverMethod<?>> observers = new ArrayList<>();
Map<Class<? extends Annotation>, Set<Annotation>> transitiveInterceptorBindings = new HashMap<>();
Map<String, Set<String>> qualifierNonbindingMembers = new HashMap<>();
this.contextReferenceFactory = contextReferenceFactory == null ? new ThreadLocalContextReferenceFactory()
: contextReferenceFactory;

applicationContext = new ApplicationContext();
singletonContext = new SingletonContext();
requestContext = new RequestContext();
requestContext = new RequestContext(this.contextReferenceFactory.create());
contexts = new HashMap<>();
putContext(requestContext);
putContext(applicationContext);
Expand Down Expand Up @@ -335,6 +340,11 @@ public void setExecutor(ExecutorService executor) {
this.executorService = executor;
}

@Override
public ContextReferenceFactory getContextReferenceFactory() {
return contextReferenceFactory;
}

@Override
public String toString() {
return "ArcContainerImpl [id=" + id + ", running=" + running + ", beans=" + beans.size() + ", observers="
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.arc.impl;

import io.quarkus.arc.ContextInstanceHandle;
import io.quarkus.arc.ContextReference;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.ManagedContext;
import io.quarkus.arc.impl.EventImpl.Notifier;
Expand Down Expand Up @@ -33,14 +34,14 @@ class RequestContext implements ManagedContext {

private static final Logger LOGGER = Logger.getLogger(RequestContext.class.getPackage().getName());

// It's a normal scope so there may be no more than one mapped instance per contextual type per thread
private final ThreadLocal<RequestContextState> currentContext = new ThreadLocal<>();
private final ContextReference<RequestContextState> currentContext;

private final LazyValue<Notifier<Object>> initializedNotifier;
private final LazyValue<Notifier<Object>> beforeDestroyedNotifier;
private final LazyValue<Notifier<Object>> destroyedNotifier;

public RequestContext() {
public RequestContext(ContextReference<RequestContextState> currentContext) {
this.currentContext = currentContext;
this.initializedNotifier = new LazyValue<>(RequestContext::createInitializedNotifier);
this.beforeDestroyedNotifier = new LazyValue<>(RequestContext::createBeforeDestroyedNotifier);
this.destroyedNotifier = new LazyValue<>(RequestContext::createDestroyedNotifier);
Expand All @@ -56,19 +57,18 @@ public Class<? extends Annotation> getScope() {
public <T> T getIfActive(Contextual<T> contextual, Function<Contextual<T>, CreationalContext<T>> creationalContextFun) {
Objects.requireNonNull(contextual, "Contextual must not be null");
Objects.requireNonNull(creationalContextFun, "CreationalContext supplier must not be null");
RequestContextState ctxState = currentContext.get();
if (ctxState == null) {
// Thread local not set - context is not active!
RequestContextState state = currentContext.get();
if (state == null) {
// Context is not active
return null;
}
Map<Contextual<?>, ContextInstanceHandle<?>> ctxMap = currentContext.get().value;
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) ctxMap.get(contextual);
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) state.map.get(contextual);
if (instance == null) {
CreationalContext<T> creationalContext = creationalContextFun.apply(contextual);
// Bean instance does not exist - create one if we have CreationalContext
instance = new ContextInstanceHandleImpl<T>((InjectableBean<T>) contextual,
contextual.create(creationalContext), creationalContext);
ctxMap.put(contextual, instance);
state.map.put(contextual, instance);
}
return instance.get();
}
Expand All @@ -88,12 +88,12 @@ public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContex
@Override
public <T> T get(Contextual<T> contextual) {
Objects.requireNonNull(contextual, "Contextual must not be null");
Map<Contextual<?>, ContextInstanceHandle<?>> ctx = currentContext.get().value;
if (ctx == null) {
// Thread local not set - context is not active!
RequestContextState state = currentContext.get();
if (state == null) {
// Context is not active
throw new ContextNotActiveException();
}
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) ctx.get(contextual);
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) state.map.get(contextual);
return instance == null ? null : instance.get();
}

Expand All @@ -104,12 +104,12 @@ public boolean isActive() {

@Override
public void destroy(Contextual<?> contextual) {
Map<Contextual<?>, ContextInstanceHandle<?>> ctx = currentContext.get().value;
if (ctx == null) {
// Thread local not set - context is not active!
RequestContextState state = currentContext.get();
if (state == null) {
// Context is not active
throw new ContextNotActiveException();
}
ContextInstanceHandle<?> instance = ctx.remove(contextual);
ContextInstanceHandle<?> instance = state.map.remove(contextual);
if (instance != null) {
instance.destroy();
}
Expand All @@ -123,7 +123,7 @@ public void activate(ContextState initialState) {
fireIfNotEmpty(initializedNotifier);
} else {
if (initialState instanceof RequestContextState) {
currentContext.set(((RequestContextState) initialState));
currentContext.set((RequestContextState) initialState);
} else {
throw new IllegalArgumentException("Invalid initial state: " + initialState.getClass().getName());
}
Expand All @@ -132,20 +132,16 @@ public void activate(ContextState initialState) {

@Override
public ContextState getState() {
RequestContextState ctx = currentContext.get();
if (ctx == null) {
RequestContextState state = currentContext.get();
if (state == null) {
// Thread local not set - context is not active!
throw new ContextNotActiveException();
}
return ctx;
return state;
}

public ContextState getStateIfActive() {
RequestContextState ctx = currentContext.get();
if (ctx == null) {
return null;
}
return ctx;
return currentContext.get();
}

@Override
Expand All @@ -167,9 +163,9 @@ public void destroy(ContextState state) {
if (state instanceof RequestContextState) {
RequestContextState reqState = ((RequestContextState) state);
reqState.isValid.set(false);
destroy(reqState.value);
destroy(reqState.map);
} else {
throw new IllegalArgumentException("Invalid state: " + state.getClass().getName());
throw new IllegalArgumentException("Invalid state implementation: " + state.getClass().getName());
}
}

Expand Down Expand Up @@ -230,17 +226,17 @@ private static Notifier<Object> createDestroyedNotifier() {

static class RequestContextState implements ContextState {

private final ConcurrentMap<Contextual<?>, ContextInstanceHandle<?>> value;
private final Map<Contextual<?>, ContextInstanceHandle<?>> map;
private final AtomicBoolean isValid;

RequestContextState(ConcurrentMap<Contextual<?>, ContextInstanceHandle<?>> value) {
this.value = value;
this.map = Objects.requireNonNull(value);
this.isValid = new AtomicBoolean(true);
}

@Override
public Map<InjectableBean<?>, Object> getContextualInstances() {
return value.values().stream()
return map.values().stream()
.collect(Collectors.toUnmodifiableMap(ContextInstanceHandle::getBean, ContextInstanceHandle::get));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.arc.impl;

import io.quarkus.arc.ContextReference;
import io.quarkus.arc.InjectableContext.ContextState;

/**
* {@link ThreadLocal} implementation of {@link ContextReference}.
*
* @param <T>
*/
final class ThreadLocalContextReference<T extends ContextState> implements ContextReference<T> {

private final ThreadLocal<T> currentContext = new ThreadLocal<>();

@Override
public T get() {
return currentContext.get();
}

@Override
public void set(T state) {
currentContext.set(state);
}

@Override
public void remove() {
currentContext.remove();
}

}
Loading

0 comments on commit 77fc6b2

Please sign in to comment.