Skip to content

Commit

Permalink
ArC: initial support for inactive synthetic beans
Browse files Browse the repository at this point in the history
  • Loading branch information
Ladicek committed Jul 12, 2024
1 parent 72af1d9 commit 626f1f9
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.ClientProxy;
import io.quarkus.arc.InactiveBeanException;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.arc.deployment.AutoAddScopeBuildItem.MatchPredicate;
Expand All @@ -37,9 +38,11 @@
import io.quarkus.arc.processor.BuildExtension;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.arc.processor.ObserverConfigurator;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.CatchBlockCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
Expand Down Expand Up @@ -175,44 +178,67 @@ private void registerStartupObserver(ObserverRegistrationPhaseBuildItem observer
configurator.id(id);
configurator.priority(priority);
configurator.notify(mc -> {
BytecodeCreator bytecode = mc;

// InjectableBean<Foo> bean = Arc.container().bean("bflmpsvz");
ResultHandle containerHandle = mc.invokeStaticMethod(ARC_CONTAINER);
ResultHandle beanHandle = mc.invokeInterfaceMethod(ARC_CONTAINER_BEAN, containerHandle,
mc.load(bean.getIdentifier()));
ResultHandle containerHandle = bytecode.invokeStaticMethod(ARC_CONTAINER);
ResultHandle beanHandle = bytecode.invokeInterfaceMethod(ARC_CONTAINER_BEAN, containerHandle,
bytecode.load(bean.getIdentifier()));

// if the [synthetic] bean is not active and is not injected in a user bean, ignore `InactiveBeanException`
// this means that an inactive bean that is injected into a user bean will end up with an error
if (bean.hasCheckActiveMethod()) {
boolean isInjectedInUserBean = false;
for (InjectionPointInfo ip : observerRegistration.getBeanProcessor().getBeanDeployment().getInjectionPoints()) {
BeanInfo resolvedBean = ip.getResolvedBean();
if (resolvedBean != null && !resolvedBean.isSynthetic() && resolvedBean.equals(bean)) {
isInjectedInUserBean = true;
break;
}
}

if (!isInjectedInUserBean) {
TryBlock tryBlock = bytecode.tryBlock();
CatchBlockCreator catchBlock = tryBlock.addCatch(InactiveBeanException.class);
catchBlock.returnVoid();
bytecode = tryBlock;
}
}

if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
// It does not make a lot of sense to support @Startup dependent beans but it's still a valid use case
ResultHandle creationalContext = mc.newInstance(
ResultHandle creationalContext = bytecode.newInstance(
MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class),
beanHandle);
// Create a dependent instance
ResultHandle instance = mc.invokeInterfaceMethod(CONTEXTUAL_CREATE, beanHandle,
ResultHandle instance = bytecode.invokeInterfaceMethod(CONTEXTUAL_CREATE, beanHandle,
creationalContext);
if (startupMethod != null) {
TryBlock tryBlock = mc.tryBlock();
TryBlock tryBlock = bytecode.tryBlock();
tryBlock.invokeVirtualMethod(MethodDescriptor.of(startupMethod), instance);
CatchBlockCreator catchBlock = tryBlock.addCatch(Exception.class);
catchBlock.invokeInterfaceMethod(CONTEXTUAL_DESTROY, beanHandle, instance, creationalContext);
catchBlock.throwException(RuntimeException.class, "Error destroying bean with @Startup method",
catchBlock.getCaughtException());
}
// Destroy the instance immediately
mc.invokeInterfaceMethod(CONTEXTUAL_DESTROY, beanHandle, instance, creationalContext);
bytecode.invokeInterfaceMethod(CONTEXTUAL_DESTROY, beanHandle, instance, creationalContext);
} else {
// Obtains the instance from the context
// InstanceHandle<Foo> handle = Arc.container().instance(bean);
ResultHandle instanceHandle = mc.invokeInterfaceMethod(ARC_CONTAINER_INSTANCE, containerHandle,
ResultHandle instanceHandle = bytecode.invokeInterfaceMethod(ARC_CONTAINER_INSTANCE, containerHandle,
beanHandle);
ResultHandle instance = mc.invokeInterfaceMethod(INSTANCE_HANDLE_GET, instanceHandle);
ResultHandle instance = bytecode.invokeInterfaceMethod(INSTANCE_HANDLE_GET, instanceHandle);
if (startupMethod != null) {
mc.invokeVirtualMethod(MethodDescriptor.of(startupMethod), instance);
bytecode.invokeVirtualMethod(MethodDescriptor.of(startupMethod), instance);
} else if (bean.getScope().isNormal()) {
// We need to unwrap the client proxy
// ((ClientProxy) handle.get()).arc_contextualInstance();
ResultHandle proxyHandle = mc.checkCast(instance, ClientProxy.class);
mc.invokeInterfaceMethod(CLIENT_PROXY_CONTEXTUAL_INSTANCE, proxyHandle);
ResultHandle proxyHandle = bytecode.checkCast(instance, ClientProxy.class);
bytecode.invokeInterfaceMethod(CLIENT_PROXY_CONTEXTUAL_INSTANCE, proxyHandle);
}
}
mc.returnValue(null);
bytecode.returnVoid();
});
configurator.done();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

Expand Down Expand Up @@ -68,6 +69,10 @@ boolean hasRecorderInstance() {
|| configurator.runtimeProxy != null;
}

boolean hasCheckActiveRunnable() {
return configurator.checkActive != null;
}

/**
* This construct is not thread-safe and should not be reused.
*/
Expand All @@ -79,6 +84,8 @@ public static class ExtendedBeanConfigurator extends BeanConfiguratorBase<Extend
private Function<SyntheticCreationalContext<?>, ?> fun;
private boolean staticInit;

private Runnable checkActive;

ExtendedBeanConfigurator(DotName implClazz) {
super(implClazz);
this.staticInit = true;
Expand All @@ -92,7 +99,11 @@ public static class ExtendedBeanConfigurator extends BeanConfiguratorBase<Extend
public SyntheticBeanBuildItem done() {
if (supplier == null && runtimeValue == null && fun == null && runtimeProxy == null && creatorConsumer == null) {
throw new IllegalStateException(
"Synthetic bean does not provide a creation method, use ExtendedBeanConfigurator#creator(), ExtendedBeanConfigurator#supplier(), ExtendedBeanConfigurator#createWith() or ExtendedBeanConfigurator#runtimeValue()");
"Synthetic bean does not provide a creation method, use ExtendedBeanConfigurator#creator(), ExtendedBeanConfigurator#supplier(), ExtendedBeanConfigurator#createWith() or ExtendedBeanConfigurator#runtimeValue()");
}
if (checkActive != null && supplier == null && runtimeValue == null && fun == null && runtimeProxy == null) {
throw new IllegalStateException(
"Synthetic bean has ExtendedBeanConfigurator#checkActive(), but does not have ExtendedBeanConfigurator#supplier() / createWith() / runtimeValue() / runtimeProxy()");
}
return new SyntheticBeanBuildItem(this);
}
Expand Down Expand Up @@ -181,6 +192,19 @@ public ExtendedBeanConfigurator setRuntimeInit() {
return this;
}

/**
* The {@link #checkActive(Consumer)} procedure is a {@link Runnable} proxy returned from a recorder method.
*
* @param checkActive a {@code Runnable} returned from a recorder method
* @return self
* @throws IllegalArgumentException If the {@code checkActive} argument is not a proxy returned from a recorder method
*/
public ExtendedBeanConfigurator checkActive(Runnable checkActive) {
checkReturnedProxy(checkActive);
this.checkActive = Objects.requireNonNull(checkActive);
return this;
}

DotName getImplClazz() {
return implClazz;
}
Expand Down Expand Up @@ -213,6 +237,10 @@ Object getRuntimeProxy() {
return runtimeProxy;
}

Runnable getCheckActive() {
return checkActive;
}

private void checkMultipleCreationMethods() {
if (runtimeProxy == null && runtimeValue == null && supplier == null && fun == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ public class SyntheticBeansProcessor {
void initStatic(ArcRecorder recorder, List<SyntheticBeanBuildItem> syntheticBeans,
BeanRegistrationPhaseBuildItem beanRegistration, BuildProducer<BeanConfiguratorBuildItem> configurators) {

Map<String, Function<SyntheticCreationalContext<?>, ?>> functionsMap = new HashMap<>();
Map<String, Function<SyntheticCreationalContext<?>, ?>> creationFunctions = new HashMap<>();
Map<String, Runnable> checkActiveRunnables = new HashMap<>();

for (SyntheticBeanBuildItem bean : syntheticBeans) {
if (bean.hasRecorderInstance() && bean.isStaticInit()) {
configureSyntheticBean(recorder, functionsMap, beanRegistration, bean);
configureSyntheticBean(recorder, creationFunctions, checkActiveRunnables, beanRegistration, bean);
}
}
// Init the map of bean instances
recorder.initStaticSupplierBeans(functionsMap);
recorder.initStaticSupplierBeans(creationFunctions, checkActiveRunnables);
}

@Record(ExecutionTime.RUNTIME_INIT)
Expand All @@ -49,14 +50,15 @@ void initStatic(ArcRecorder recorder, List<SyntheticBeanBuildItem> syntheticBean
ServiceStartBuildItem initRuntime(ArcRecorder recorder, List<SyntheticBeanBuildItem> syntheticBeans,
BeanRegistrationPhaseBuildItem beanRegistration, BuildProducer<BeanConfiguratorBuildItem> configurators) {

Map<String, Function<SyntheticCreationalContext<?>, ?>> functionsMap = new HashMap<>();
Map<String, Function<SyntheticCreationalContext<?>, ?>> creationFunctions = new HashMap<>();
Map<String, Runnable> checkActiveRunnables = new HashMap<>();

for (SyntheticBeanBuildItem bean : syntheticBeans) {
if (bean.hasRecorderInstance() && !bean.isStaticInit()) {
configureSyntheticBean(recorder, functionsMap, beanRegistration, bean);
configureSyntheticBean(recorder, creationFunctions, checkActiveRunnables, beanRegistration, bean);
}
}
recorder.initRuntimeSupplierBeans(functionsMap);
recorder.initRuntimeSupplierBeans(creationFunctions, checkActiveRunnables);
return new ServiceStartBuildItem("runtime-bean-init");
}

Expand All @@ -66,29 +68,34 @@ void initRegular(List<SyntheticBeanBuildItem> syntheticBeans,

for (SyntheticBeanBuildItem bean : syntheticBeans) {
if (!bean.hasRecorderInstance()) {
configureSyntheticBean(null, null, beanRegistration, bean);
configureSyntheticBean(null, null, null, beanRegistration, bean);
}
}
}

private void configureSyntheticBean(ArcRecorder recorder,
Map<String, Function<SyntheticCreationalContext<?>, ?>> functionsMap,
BeanRegistrationPhaseBuildItem beanRegistration, SyntheticBeanBuildItem bean) {
Map<String, Function<SyntheticCreationalContext<?>, ?>> creationFunctions,
Map<String, Runnable> checkActiveRunnables, BeanRegistrationPhaseBuildItem beanRegistration,
SyntheticBeanBuildItem bean) {
String name = createName(bean.configurator());
if (bean.configurator().getRuntimeValue() != null) {
functionsMap.put(name, recorder.createFunction(bean.configurator().getRuntimeValue()));
creationFunctions.put(name, recorder.createFunction(bean.configurator().getRuntimeValue()));
} else if (bean.configurator().getSupplier() != null) {
functionsMap.put(name, recorder.createFunction(bean.configurator().getSupplier()));
creationFunctions.put(name, recorder.createFunction(bean.configurator().getSupplier()));
} else if (bean.configurator().getFunction() != null) {
functionsMap.put(name, bean.configurator().getFunction());
creationFunctions.put(name, bean.configurator().getFunction());
} else if (bean.configurator().getRuntimeProxy() != null) {
functionsMap.put(name, recorder.createFunction(bean.configurator().getRuntimeProxy()));
creationFunctions.put(name, recorder.createFunction(bean.configurator().getRuntimeProxy()));
}
BeanConfigurator<?> configurator = beanRegistration.getContext().configure(bean.configurator().getImplClazz())
.read(bean.configurator());
if (bean.hasRecorderInstance()) {
configurator.creator(creator(name, bean));
}
if (bean.hasCheckActiveRunnable()) {
configurator.checkActive(checkActive(name, bean));
checkActiveRunnables.put(name, bean.configurator().getCheckActive());
}
configurator.done();
}

Expand Down Expand Up @@ -118,6 +125,26 @@ public void accept(MethodCreator m) {
};
}

private Consumer<MethodCreator> checkActive(String name, SyntheticBeanBuildItem bean) {
return new Consumer<MethodCreator>() {
@Override
public void accept(MethodCreator mc) {
ResultHandle staticMap = mc.readStaticField(
FieldDescriptor.of(ArcRecorder.class, "syntheticBeanCheckActive", Map.class));
ResultHandle runnable = mc.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class),
staticMap, mc.load(name));
// TODO different message, possibly even different exception
mc.ifNull(runnable).trueBranch().throwException(CreationException.class,
createMessage(name, bean));
mc.invokeInterfaceMethod(
MethodDescriptor.ofMethod(Runnable.class, "run", void.class),
runnable);
mc.returnVoid();
}
};
}

private String createMessage(String name, SyntheticBeanBuildItem bean) {
StringBuilder builder = new StringBuilder();
builder.append("Synthetic bean instance for ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public class ArcRecorder {
*/
public static volatile Map<String, Function<SyntheticCreationalContext<?>, ?>> syntheticBeanProviders;

public static volatile Map<String, Runnable> syntheticBeanCheckActive;

public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue<CurrentContextFactory> currentContextFactory,
boolean strictCompatibility) throws Exception {
ArcInitConfig.Builder builder = ArcInitConfig.builder();
Expand All @@ -61,12 +63,16 @@ public void initExecutor(ExecutorService executor) {
Arc.setExecutor(executor);
}

public void initStaticSupplierBeans(Map<String, Function<SyntheticCreationalContext<?>, ?>> beans) {
syntheticBeanProviders = new ConcurrentHashMap<>(beans);
public void initStaticSupplierBeans(Map<String, Function<SyntheticCreationalContext<?>, ?>> creationFunctions,
Map<String, Runnable> checkActiveRunnables) {
syntheticBeanProviders = new ConcurrentHashMap<>(creationFunctions);
syntheticBeanCheckActive = new ConcurrentHashMap<>(checkActiveRunnables);
}

public void initRuntimeSupplierBeans(Map<String, Function<SyntheticCreationalContext<?>, ?>> beans) {
syntheticBeanProviders.putAll(beans);
public void initRuntimeSupplierBeans(Map<String, Function<SyntheticCreationalContext<?>, ?>> creationFunctions,
Map<String, Runnable> checkActiveRunnables) {
syntheticBeanProviders.putAll(creationFunctions);
syntheticBeanCheckActive.putAll(checkActiveRunnables);
}

public BeanContainer initBeanContainer(ArcContainer container, List<BeanContainerListener> listeners)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ public void done() {
.forceApplicationClass(forceApplicationClass)
.targetPackageName(targetPackageName)
.startupPriority(startupPriority)
.interceptionProxy(interceptionProxy);
.interceptionProxy(interceptionProxy)
.checkActive(checkActiveConsumer);

if (!injectionPoints.isEmpty()) {
builder.injections(Collections.singletonList(Injection.forSyntheticBean(injectionPoints)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public abstract class BeanConfiguratorBase<THIS extends BeanConfiguratorBase<THI
protected final Set<TypeAndQualifiers> injectionPoints;
protected Integer startupPriority;
protected InterceptionProxyInfo interceptionProxy;
protected Consumer<MethodCreator> checkActiveConsumer;

protected BeanConfiguratorBase(DotName implClazz) {
this.implClazz = implClazz;
Expand Down Expand Up @@ -101,6 +102,7 @@ public THIS read(BeanConfiguratorBase<?, ?> base) {
injectionPoints.addAll(base.injectionPoints);
startupPriority = base.startupPriority;
interceptionProxy = base.interceptionProxy;
checkActiveConsumer = base.checkActiveConsumer;
return self();
}

Expand Down Expand Up @@ -391,6 +393,20 @@ public THIS destroyer(Consumer<MethodCreator> methodCreatorConsumer) {
return cast(this);
}

public THIS checkActive(Class<? extends Runnable> checkerClazz) {
return checkActive(mc -> {
// new FooRunnable().run()
ResultHandle checkerHandle = mc.newInstance(MethodDescriptor.ofConstructor(checkerClazz));
mc.invokeInterfaceMethod(MethodDescriptor.ofMethod(Runnable.class, "run", void.class), checkerHandle);
mc.returnVoid();
});
}

public THIS checkActive(Consumer<MethodCreator> methodCreatorConsumer) {
this.checkActiveConsumer = methodCreatorConsumer;
return cast(this);
}

/**
* The identifier becomes part of the {@link BeanInfo#getIdentifier()} and {@link InjectableBean#getIdentifier()}.
* <p>
Expand Down
Loading

0 comments on commit 626f1f9

Please sign in to comment.