diff --git a/bom/application/pom.xml b/bom/application/pom.xml index d3467d2d5071b..d73d66ee698df 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -47,7 +47,7 @@ 1.3.4 4.3.2 2.4.2 - 1.0.19 + 1.1.0 1.0.13 1.4.0 2.7.1 @@ -3049,6 +3049,11 @@ smallrye-context-propagation-jta ${smallrye-context-propagation.version} + + io.smallrye + smallrye-context-propagation-storage + ${smallrye-context-propagation.version} + io.smallrye smallrye-jwt diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/StorageReadyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/StorageReadyBuildItem.java new file mode 100644 index 0000000000000..b2ac03831caf4 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/StorageReadyBuildItem.java @@ -0,0 +1,7 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class StorageReadyBuildItem extends SimpleBuildItem { + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/storage/StorageBuildProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/storage/StorageBuildProcessor.java new file mode 100644 index 0000000000000..778a2cb7200ad --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/storage/StorageBuildProcessor.java @@ -0,0 +1,235 @@ +package io.quarkus.deployment.storage; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; +import org.objectweb.asm.Opcodes; + +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.StorageReadyBuildItem; +import io.quarkus.deployment.recording.RecorderContext; +import io.quarkus.deployment.util.AsmUtil; +import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.storage.QuarkusThread; +import io.quarkus.runtime.storage.StorageRecorder; +import io.smallrye.context.storage.spi.StorageDeclaration; + +public class StorageBuildProcessor { + + public final class StorageFieldInfo { + public final String typeSignature; + public final String rawType; + public final int index; + public final Type type; + public final ClassInfo threadLocalStorageClassInfo; + + public StorageFieldInfo(int index, Type type, ClassInfo threadLocalStorageClassInfo) { + this.index = index; + this.type = type; + this.threadLocalStorageClassInfo = threadLocalStorageClassInfo; + // type.name() is the raw type + rawType = type.name().toString('.'); + typeSignature = AsmUtil.getSignature(type, v -> null); + + } + } + + private static final String QUARKUS_STORAGE_IMPL_CLASS_NAME_PREFIX = "io.quarkus.deployment.storage.QuarkusStorageImpl__"; + private static final DotName DOTNAME_STORAGE_DECLARATION = DotName.createSimple(StorageDeclaration.class.getName()); + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + public StorageReadyBuildItem setupStorage(StorageRecorder recorder, + RecorderContext recorderContext, + CombinedIndexBuildItem combinedIndex, + BuildProducer generatedClass) + throws ClassNotFoundException, IOException { + + List fields = new ArrayList<>(); + int index = 0; + + for (ClassInfo threadLocalStorageClassInfo : combinedIndex.getIndex() + .getAllKnownImplementors(DOTNAME_STORAGE_DECLARATION)) { + Type storageType = null; + for (Type superClassType : threadLocalStorageClassInfo.interfaceTypes()) { + if (superClassType.kind() != Kind.PARAMETERIZED_TYPE) + continue; + ParameterizedType parameterizedType = superClassType.asParameterizedType(); + if (!parameterizedType.name().equals(DOTNAME_STORAGE_DECLARATION)) + continue; + if (parameterizedType.arguments().size() != 1) + throw storageValidation(threadLocalStorageClassInfo); + storageType = parameterizedType.arguments().get(0); + break; + } + if (storageType == null) + throw storageValidation(threadLocalStorageClassInfo); + System.err.println("Adding context element for " + threadLocalStorageClassInfo + " -> " + index); + fields.add(new StorageFieldInfo(index++, + storageType, + threadLocalStorageClassInfo)); + } + + ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); + + Map>> storageMappings = new HashMap<>(); + + // now create our ThreadLocal per field + for (StorageFieldInfo storageFieldInfo : fields) { + String className = QUARKUS_STORAGE_IMPL_CLASS_NAME_PREFIX + storageFieldInfo.index; + System.err.println("Producing " + className + " for index " + storageFieldInfo.index); + try (ClassCreator clazz = ClassCreator.builder().classOutput(classOutput) + .className(className) + .superClass(ThreadLocal.class) + .build()) { + // Ljava/lang/ThreadLocal;Ljava/lang/Object;>;>;>; + clazz.setSignature("L" + ThreadLocal.class.getName().replace('.', '/') + "<" + + storageFieldInfo.typeSignature + ">;"); + + try (MethodCreator method = clazz.getMethodCreator("get", storageFieldInfo.rawType)) { + method.setModifiers(Opcodes.ACC_PUBLIC); + // signature: ()Ljava/util/List;Ljava/lang/Object;>;>; + method.setSignature("()" + storageFieldInfo.typeSignature); + // GENERATED: + // public List, Object>> get() { + // Thread currentThread = Thread.currentThread(); + // if (currentThread instanceof QuarkusThread) { + // return (List)((QuarkusThread) currentThread).getQuarkusThreadContext()[index]; + // } else { + // return super.get(); + // } + // } + AssignableResultHandle threadVariable = method.createVariable(Thread.class); + method.assign(threadVariable, + method.invokeStaticMethod(MethodDescriptor.ofMethod(Thread.class, "currentThread", Thread.class))); + BranchResult test = method.ifNonZero(method.instanceOf(threadVariable, QuarkusThread.class)); + try (BytecodeCreator ifTrue = test.trueBranch()) { + ResultHandle baseContexts = ifTrue.invokeInterfaceMethod( + MethodDescriptor.ofMethod(QuarkusThread.class, "getQuarkusThreadContext", + Object[].class), + ifTrue.checkCast(threadVariable, QuarkusThread.class)); + ResultHandle fieldContext = ifTrue.checkCast( + ifTrue.readArrayValue(baseContexts, storageFieldInfo.index), + storageFieldInfo.rawType); + ifTrue.returnValue(fieldContext); + } + try (BytecodeCreator ifFalse = test.falseBranch()) { + ResultHandle val = ifFalse.invokeSpecialMethod( + MethodDescriptor.ofMethod(ThreadLocal.class, "get", Object.class), ifFalse.getThis()); + ifFalse.returnValue(ifFalse.checkCast(val, storageFieldInfo.rawType)); + } + } + // bridge + try (MethodCreator method = clazz.getMethodCreator("get", Object.class)) { + method.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC); + method.returnValue( + method.invokeVirtualMethod(MethodDescriptor.ofMethod(className, "get", storageFieldInfo.rawType), + method.getThis())); + } + try (MethodCreator method = clazz.getMethodCreator("set", void.class, storageFieldInfo.rawType)) { + method.setModifiers(Opcodes.ACC_PUBLIC); + // signature: (Ljava/util/List;Ljava/lang/Object;>;>;)V + method.setSignature("(" + storageFieldInfo.typeSignature + ")V"); + // GENERATED + // public void set(List, Object>> t) { + // Thread currentThread = Thread.currentThread(); + // if (currentThread instanceof QuarkusThread) { + // ((QuarkusThread) currentThread).getQuarkusThreadContext()[index] = t; + // } else { + // super.set(t); + // } + // } + AssignableResultHandle threadVariable = method.createVariable(Thread.class); + method.assign(threadVariable, + method.invokeStaticMethod(MethodDescriptor.ofMethod(Thread.class, "currentThread", Thread.class))); + BranchResult test = method.ifNonZero(method.instanceOf(threadVariable, QuarkusThread.class)); + try (BytecodeCreator ifTrue = test.trueBranch()) { + ResultHandle baseContexts = ifTrue.invokeInterfaceMethod( + MethodDescriptor.ofMethod(QuarkusThread.class, "getQuarkusThreadContext", + Object[].class), + ifTrue.checkCast(threadVariable, QuarkusThread.class)); + ifTrue.writeArrayValue(baseContexts, storageFieldInfo.index, ifTrue.getMethodParam(0)); + } + try (BytecodeCreator ifFalse = test.falseBranch()) { + ifFalse.invokeSpecialMethod( + MethodDescriptor.ofMethod(ThreadLocal.class, "set", void.class, Object.class), + ifFalse.getThis(), ifFalse.getMethodParam(0)); + } + method.returnValue(method.loadNull()); + } + // bridge + try (MethodCreator method = clazz.getMethodCreator("set", void.class, Object.class)) { + method.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC); + method.returnValue(method.invokeVirtualMethod( + MethodDescriptor.ofMethod(className, "set", void.class, storageFieldInfo.rawType), + method.getThis(), + method.checkCast(method.getMethodParam(0), storageFieldInfo.rawType))); + method.returnValue(method.loadNull()); + } + try (MethodCreator method = clazz.getMethodCreator("remove", void.class)) { + method.setModifiers(Opcodes.ACC_PUBLIC); + // GENERATED + // public void remove() { + // Thread currentThread = Thread.currentThread(); + // if (currentThread instanceof QuarkusThread) { + // ((QuarkusThread) currentThread).getQuarkusThreadContext()[index] = null; + // } else { + // super.remove(); + // } + // } + AssignableResultHandle threadVariable = method.createVariable(Thread.class); + method.assign(threadVariable, + method.invokeStaticMethod(MethodDescriptor.ofMethod(Thread.class, "currentThread", Thread.class))); + BranchResult test = method.ifNonZero(method.instanceOf(threadVariable, QuarkusThread.class)); + try (BytecodeCreator ifTrue = test.trueBranch()) { + ResultHandle baseContexts = ifTrue.invokeInterfaceMethod( + MethodDescriptor.ofMethod(QuarkusThread.class, "getQuarkusThreadContext", + Object[].class), + ifTrue.checkCast(threadVariable, QuarkusThread.class)); + ifTrue.writeArrayValue(baseContexts, storageFieldInfo.index, ifTrue.loadNull()); + } + try (BytecodeCreator ifFalse = test.falseBranch()) { + ifFalse.invokeSpecialMethod(MethodDescriptor.ofMethod(ThreadLocal.class, "remove", void.class), + ifFalse.getThis()); + } + method.returnValue(method.loadNull()); + } + } + + storageMappings.put(storageFieldInfo.threadLocalStorageClassInfo.name().toString(), + recorderContext.newInstance(className)); + } + + recorder.configureStaticInit(storageMappings); + + return new StorageReadyBuildItem(); + } + + private RuntimeException storageValidation(ClassInfo threadLocalStorageClassInfo) { + return new IllegalStateException( + "ThreadLocalStorage class must be a non-raw class implementing StorageDeclaration: " + + threadLocalStorageClassInfo); + } +} diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 983f97e025e12..bbc002d08721c 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -49,6 +49,10 @@ + + io.smallrye + smallrye-context-propagation-storage + org.jboss.logging jboss-logging diff --git a/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusStorageManager.java b/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusStorageManager.java new file mode 100644 index 0000000000000..bac72773d99fa --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusStorageManager.java @@ -0,0 +1,34 @@ +package io.quarkus.runtime.storage; + +import java.util.Map; + +import io.smallrye.context.storage.spi.StorageDeclaration; +import io.smallrye.context.storage.spi.StorageManager; + +public class QuarkusStorageManager implements StorageManager { + + private Map> declaredStorages; + private int contextCount; + + QuarkusStorageManager(Map> declaredStorages) { + this.contextCount = declaredStorages.size(); + this.declaredStorages = declaredStorages; + } + + @SuppressWarnings("unchecked") + @Override + public , X> ThreadLocal getThreadLocal(Class klass) { + ThreadLocal storage = declaredStorages.get(klass.getName()); + if (storage != null) + return (ThreadLocal) storage; + throw new IllegalArgumentException("Storage user nor registered: " + klass); + } + + public static QuarkusStorageManager instance() { + return (QuarkusStorageManager) StorageManager.instance(); + } + + public Object[] newContext() { + return new Object[contextCount]; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusStorageManagerProvider.java b/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusStorageManagerProvider.java new file mode 100644 index 0000000000000..165a92d87de48 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusStorageManagerProvider.java @@ -0,0 +1,24 @@ +package io.quarkus.runtime.storage; + +import java.util.Map; + +import io.smallrye.context.storage.spi.StorageManager; +import io.smallrye.context.storage.spi.StorageManagerProvider; + +public class QuarkusStorageManagerProvider implements StorageManagerProvider { + + private final QuarkusStorageManager storageManager; + + public QuarkusStorageManagerProvider(Map> declaredStorages) { + storageManager = new QuarkusStorageManager(declaredStorages); + } + + @Override + public StorageManager getStorageManager(ClassLoader classloader) { + return storageManager; + } + + public QuarkusStorageManagerProvider instance() { + return (QuarkusStorageManagerProvider) StorageManagerProvider.instance(); + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusThread.java b/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusThread.java new file mode 100644 index 0000000000000..f98b5b8ef69ca --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/storage/QuarkusThread.java @@ -0,0 +1,6 @@ +package io.quarkus.runtime.storage; + +public interface QuarkusThread { + + Object[] getQuarkusThreadContext(); +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/storage/StorageRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/storage/StorageRecorder.java new file mode 100644 index 0000000000000..9fad1222097c1 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/storage/StorageRecorder.java @@ -0,0 +1,24 @@ +package io.quarkus.runtime.storage; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import io.smallrye.context.storage.spi.StorageManagerProvider; + +@Recorder +public class StorageRecorder { + + public void configureStaticInit(Map>> storageMappings) { + System.err.println("Configuring storages for: " + storageMappings); + Map> declaredStorages = new HashMap<>(); + for (Entry>> entry : storageMappings.entrySet()) { + declaredStorages.put(entry.getKey(), entry.getValue().getValue()); + } + QuarkusStorageManagerProvider provider = new QuarkusStorageManagerProvider(declaredStorages); + StorageManagerProvider.register(provider); + } + +} diff --git a/extensions/smallrye-context-propagation/deployment/pom.xml b/extensions/smallrye-context-propagation/deployment/pom.xml index 1cc9af0cfcb62..d5405429cb258 100644 --- a/extensions/smallrye-context-propagation/deployment/pom.xml +++ b/extensions/smallrye-context-propagation/deployment/pom.xml @@ -24,6 +24,16 @@ io.quarkus quarkus-smallrye-context-propagation + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-vertx-http-deployment + test + diff --git a/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java index 209400682545e..7e10bb2552a72 100644 --- a/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java +++ b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationProcessor.java @@ -70,6 +70,7 @@ void build(SmallRyeContextPropagationRecorder recorder, ExecutorBuildItem executorBuildItem, BuildProducer feature, BuildProducer managedExecutorInitialized, + BuildProducer marker, BuildProducer syntheticBeans) { feature.produce(new FeatureBuildItem(Feature.SMALLRYE_CONTEXT_PROPAGATION)); @@ -87,5 +88,6 @@ void build(SmallRyeContextPropagationRecorder recorder, // This should be removed at some point after Quarkus 1.7 managedExecutorInitialized.produce(new ManagedExecutorInitializedBuildItem()); + marker.produce(new SmallRyeContextPropagationRuntimeInitialisedBuildItem()); } } diff --git a/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationRuntimeInitialisedBuildItem.java b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationRuntimeInitialisedBuildItem.java new file mode 100644 index 0000000000000..55beecb5f7245 --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/main/java/io/quarkus/smallrye/context/deployment/SmallRyeContextPropagationRuntimeInitialisedBuildItem.java @@ -0,0 +1,10 @@ +package io.quarkus.smallrye.context.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Marker build item to be able to run after SR-CP has had time to configure itself at runtime. + */ +public final class SmallRyeContextPropagationRuntimeInitialisedBuildItem extends SimpleBuildItem { + +} diff --git a/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/smallrye/context/test/CPTest.java b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/smallrye/context/test/CPTest.java new file mode 100644 index 0000000000000..e1e43b935d795 --- /dev/null +++ b/extensions/smallrye-context-propagation/deployment/src/test/java/io/quarkus/smallrye/context/test/CPTest.java @@ -0,0 +1,67 @@ +package io.quarkus.smallrye.context.test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.runtime.storage.QuarkusThread; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.context.SmallRyeThreadContext; +import io.vertx.core.Vertx; + +public class CPTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .overrideConfigKey("quarkus.vertx.storage", "false") + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(); + } + }); + + @Inject + SmallRyeThreadContext tc; + + @Inject + Vertx vertx; + + @Test + public void test() throws InterruptedException, ExecutionException { + System.err.println("Current thread: " + Thread.currentThread().getClass()); + System.err.println("Plan: " + tc.getPlan().propagatedProviders); + Arc.container().requestContext().activate(); + System.err.println("Request context: " + Arc.container().requestContext().isActive()); + + Runnable runner = tc.contextualRunnable(() -> { + System.err.println("Runner thread supports storage: " + (Thread.currentThread() instanceof QuarkusThread)); + System.err.println("Runner request context: " + Arc.container().requestContext().isActive()); + System.err.println("Runner called with context"); + }); + + CompletableFuture cf = new CompletableFuture<>(); + vertx.executeBlocking(prom -> { + runner.run(); + prom.complete(); + }, res -> { + if (res.succeeded()) + cf.complete(res.result()); + else + cf.completeExceptionally(res.cause()); + System.err.println("Got result: " + res); + }); + cf.get(); + System.err.println("Done"); + Arc.container().requestContext().destroy(); + } +} diff --git a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java index def1211fa46dc..dc8e9624a3b52 100644 --- a/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java +++ b/extensions/smallrye-context-propagation/runtime/src/main/java/io/quarkus/smallrye/context/runtime/SmallRyeContextPropagationRecorder.java @@ -42,13 +42,12 @@ public void configureStaticInit(List discoveredProviders, public void configureRuntime(ExecutorService executorService) { // associate the static init manager to the runtime CL - ContextManagerProvider contextManagerProvider = ContextManagerProvider.instance(); // finish building our manager builder.withDefaultExecutorService(executorService); + builder.forClassLoader(Thread.currentThread().getContextClassLoader()); + builder.registerOnProvider(); - SmallRyeContextManager contextManager = builder.build(); - - contextManagerProvider.registerContextManager(contextManager, Thread.currentThread().getContextClassLoader()); + builder.build(); } public Supplier initializeManagedExecutor(ExecutorService executorService) {