diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 068229601463c..b3094aa5f4678 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -38,7 +38,7 @@
2.0
1.2
1.0
- 1.9.0
+ 1.10.0
2.9.0
3.2.0
3.0.4
diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyClientInjectionTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyClientInjectionTest.java
index d7ef443e16c63..b6ae1d2dc51d4 100644
--- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyClientInjectionTest.java
+++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyClientInjectionTest.java
@@ -19,6 +19,7 @@
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.server.services.HelloService;
import io.quarkus.test.QuarkusUnitTest;
+import io.smallrye.common.vertx.VertxContext;
import io.smallrye.mutiny.Uni;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.impl.EventLoopContext;
@@ -80,7 +81,7 @@ public String invokeFromIoThread(String s) {
public String invokeFromDuplicatedContext(String s) {
Context root = vertx.getOrCreateContext();
- ContextInternal duplicate = ((ContextInternal) root.getDelegate()).duplicate();
+ ContextInternal duplicate = (ContextInternal) VertxContext.getOrCreateDuplicatedContext(root.getDelegate());
return Uni.createFrom(). emitter(e -> {
duplicate.runOnContext(x -> {
service.sayHello(HelloRequest.newBuilder().setName(s).build())
diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyStubInjectionTest.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyStubInjectionTest.java
index 47094ec329483..31ea175e08ad4 100644
--- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyStubInjectionTest.java
+++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/client/MutinyStubInjectionTest.java
@@ -21,8 +21,8 @@
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.server.services.HelloService;
import io.quarkus.test.QuarkusUnitTest;
+import io.smallrye.common.vertx.VertxContext;
import io.smallrye.mutiny.Uni;
-import io.vertx.core.impl.ContextInternal;
import io.vertx.core.impl.EventLoopContext;
import io.vertx.core.impl.WorkerContext;
import io.vertx.mutiny.core.Context;
@@ -88,7 +88,7 @@ public String invokeFromIoThread(String s) {
public String invokeFromDuplicatedContext(String s) {
Context root = vertx.getOrCreateContext();
- ContextInternal duplicate = ((ContextInternal) root.getDelegate()).duplicate();
+ io.vertx.core.Context duplicate = VertxContext.getOrCreateDuplicatedContext(root.getDelegate());
return Uni.createFrom(). emitter(e -> {
duplicate.runOnContext(x -> {
service.sayHello(HelloRequest.newBuilder().setName(s).build())
diff --git a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/services/AssertHelper.java b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/services/AssertHelper.java
index e8461260eb4da..0feb45b6d4c49 100644
--- a/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/services/AssertHelper.java
+++ b/extensions/grpc/deployment/src/test/java/io/quarkus/grpc/server/services/AssertHelper.java
@@ -3,10 +3,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import io.quarkus.arc.Arc;
+import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
-import io.vertx.core.impl.EventLoopContext;
-import io.vertx.core.impl.WorkerContext;
public class AssertHelper {
@@ -16,20 +15,15 @@ public static void assertThatTheRequestScopeIsActive() {
public static void assertRunOnEventLoop() {
assertThat(Vertx.currentContext()).isNotNull();
- assertThat(Vertx.currentContext().isEventLoopContext());
+ assertThat(Vertx.currentContext().isEventLoopContext()).isTrue();
assertThat(Thread.currentThread().getName()).contains("eventloop");
}
public static Context assertRunOnDuplicatedContext() {
- assertThat(Vertx.currentContext()).isNotNull();
- assertThat(isRootContext(Vertx.currentContext())).isFalse();
+ assertThat(VertxContext.isOnDuplicatedContext()).isTrue();
return Vertx.currentContext();
}
- private static boolean isRootContext(Context context) {
- return context instanceof EventLoopContext || context instanceof WorkerContext;
- }
-
public static void assertRunOnWorker() {
assertThat(Vertx.currentContext()).isNotNull();
assertThat(Thread.currentThread().getName()).contains("executor");
diff --git a/extensions/grpc/runtime/pom.xml b/extensions/grpc/runtime/pom.xml
index a06bc5f6449ea..0c4baa4be920a 100644
--- a/extensions/grpc/runtime/pom.xml
+++ b/extensions/grpc/runtime/pom.xml
@@ -85,6 +85,10 @@
io.smallrye.common
smallrye-common-annotation
+
+ io.smallrye.common
+ smallrye-common-vertx-context
+
diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java
index 78983dcad303f..b6f0938dafeac 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/context/GrpcDuplicatedContextGrpcInterceptor.java
@@ -12,11 +12,9 @@
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.quarkus.grpc.GlobalInterceptor;
+import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
-import io.vertx.core.impl.ContextInternal;
-import io.vertx.core.impl.EventLoopContext;
-import io.vertx.core.impl.WorkerContext;
@ApplicationScoped
@GlobalInterceptor
@@ -27,7 +25,7 @@ public GrpcDuplicatedContextGrpcInterceptor() {
}
private static boolean isRootContext(Context context) {
- return context instanceof EventLoopContext || context instanceof WorkerContext;
+ return !VertxContext.isDuplicatedContext(context);
}
@Override
@@ -40,10 +38,7 @@ public ServerCall.Listener interceptCall(ServerCall(() -> next.startCall(call, headers), local);
diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java b/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java
index 8dacc413feefd..f9f45c0fa7cd2 100644
--- a/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java
+++ b/extensions/opentelemetry/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/OpenTelemetryProcessor.java
@@ -34,7 +34,6 @@
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
-import io.quarkus.vertx.deployment.CopyVertxContextDataBuildItem;
public class OpenTelemetryProcessor {
static class OpenTelemetryEnabled implements BooleanSupplier {
@@ -138,11 +137,6 @@ void storeVertxOnContextStorage(OpenTelemetryRecorder recorder, CoreVertxBuildIt
recorder.storeVertxOnContextStorage(vertx.getVertx());
}
- @BuildStep
- CopyVertxContextDataBuildItem copyVertxContextData() {
- return new CopyVertxContextDataBuildItem(QuarkusContextStorage.ACTIVE_CONTEXT);
- }
-
public static boolean isClassPresent(String classname) {
try {
Class.forName(classname, false, Thread.currentThread().getContextClassLoader());
diff --git a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java
index 19575b397e21b..324c16bba89d6 100644
--- a/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java
+++ b/extensions/opentelemetry/opentelemetry/deployment/src/test/java/io/quarkus/opentelemetry/deployment/WithSpanInterceptorTest.java
@@ -2,12 +2,18 @@
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
import static io.opentelemetry.api.trace.SpanKind.SERVER;
+import static java.net.HttpURLConnection.HTTP_OK;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
import javax.inject.Inject;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
@@ -19,7 +25,10 @@
import io.opentelemetry.extension.annotations.SpanAttribute;
import io.opentelemetry.extension.annotations.WithSpan;
import io.opentelemetry.sdk.trace.data.SpanData;
+import io.quarkus.runtime.StartupEvent;
import io.quarkus.test.QuarkusUnitTest;
+import io.smallrye.config.SmallRyeConfig;
+import io.vertx.ext.web.Router;
public class WithSpanInterceptorTest {
@RegisterExtension
@@ -79,6 +88,15 @@ void spanChild() {
assertEquals(spanItems.get(0).getParentSpanId(), spanItems.get(1).getSpanId());
}
+ @Test
+ void spanCdiRest() {
+ spanBean.spanRestClient();
+ List spanItems = spanExporter.getFinishedSpanItems(4);
+ assertEquals(spanItems.get(0).getTraceId(), spanItems.get(1).getTraceId());
+ assertEquals(spanItems.get(0).getTraceId(), spanItems.get(2).getTraceId());
+ assertEquals(spanItems.get(0).getTraceId(), spanItems.get(3).getTraceId());
+ }
+
@ApplicationScoped
public static class SpanBean {
@WithSpan
@@ -108,6 +126,14 @@ public void spanArgs(@SpanAttribute(value = "arg") String arg) {
public void spanChild() {
spanChildBean.spanChild();
}
+
+ @Inject
+ SpanRestClient spanRestClient;
+
+ @WithSpan
+ public void spanRestClient() {
+ spanRestClient.spanRestClient();
+ }
}
@ApplicationScoped
@@ -117,4 +143,28 @@ public void spanChild() {
}
}
+
+ @ApplicationScoped
+ public static class SpanRestClient {
+ @Inject
+ SmallRyeConfig config;
+
+ @WithSpan
+ public void spanRestClient() {
+ WebTarget target = ClientBuilder.newClient()
+ .target(UriBuilder.fromUri(config.getRawValue("test.url")).path("hello"));
+ Response response = target.request().get();
+ assertEquals(HTTP_OK, response.getStatus());
+ }
+ }
+
+ @ApplicationScoped
+ public static class HelloRouter {
+ @Inject
+ Router router;
+
+ public void register(@Observes StartupEvent ev) {
+ router.get("/hello").handler(rc -> rc.response().end("hello"));
+ }
+ }
}
diff --git a/extensions/opentelemetry/opentelemetry/runtime/pom.xml b/extensions/opentelemetry/opentelemetry/runtime/pom.xml
index 3669b4f1cdab7..4425bf1fdd2ab 100644
--- a/extensions/opentelemetry/opentelemetry/runtime/pom.xml
+++ b/extensions/opentelemetry/opentelemetry/runtime/pom.xml
@@ -39,6 +39,10 @@
true
+
+ io.smallrye.common
+ smallrye-common-vertx-context
+
io.opentelemetry
opentelemetry-sdk
diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java
index 3ef55023745c6..091871ba873aa 100644
--- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java
+++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/QuarkusContextStorage.java
@@ -1,66 +1,113 @@
package io.quarkus.opentelemetry.runtime;
+import static io.smallrye.common.vertx.VertxContext.getOrCreateDuplicatedContext;
+import static io.smallrye.common.vertx.VertxContext.isDuplicatedContext;
+
import org.jboss.logging.Logger;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.Scope;
import io.vertx.core.Vertx;
-import io.vertx.core.impl.ContextInternal;
+/**
+ * Bridges the OpenTelemetry ContextStorage with the Vert.x Context. The default OpenTelemetry ContextStorage (based in
+ * ThreadLocals) is not suitable for Vert.x. In this case, the OpenTelemetry Context piggybacks on top of the Vert.x
+ * Context. If the Vert.x Context is not available, fallbacks to the default OpenTelemetry ContextStorage.
+ */
public enum QuarkusContextStorage implements ContextStorage {
INSTANCE;
private static final Logger log = Logger.getLogger(QuarkusContextStorage.class);
+ private static final String OTEL_CONTEXT = QuarkusContextStorage.class.getName() + ".otelContext";
- public static final String ACTIVE_CONTEXT = QuarkusContextStorage.class.getName() + ".activeContext";
-
+ private static final ContextStorage DEFAULT_CONTEXT_STORAGE = ContextStorage.defaultStorage();
static Vertx vertx;
+ /**
+ * Attach the OpenTelemetry Context to the current Context. If a Vert.x Context is available, and it is a duplicated
+ * Vert.x Context the OpenTelemetry Context is attached to the Vert.x Context. Otherwise, fallback to the
+ * OpenTelemetry default ContextStorage.
+ *
+ * @param toAttach the OpenTelemetry Context to attach
+ * @return the Scope of the OpenTelemetry Context
+ */
@Override
public Scope attach(Context toAttach) {
- return attach(getVertxContext(), toAttach);
+ io.vertx.core.Context vertxContext = getVertxContext();
+ return vertxContext != null && isDuplicatedContext(vertxContext) ? attach(vertxContext, toAttach)
+ : DEFAULT_CONTEXT_STORAGE.attach(toAttach);
}
+ /**
+ * Attach the OpenTelemetry Context in the Vert.x Context if it is a duplicated Vert.x Context.
+ *
+ * @param vertxContext the Vert.x Context to attach the OpenTelemetry Context
+ * @param toAttach the OpenTelemetry Context to attach
+ * @return the Scope of the OpenTelemetry Context
+ */
public Scope attach(io.vertx.core.Context vertxContext, Context toAttach) {
- if (toAttach == null) {
- // Not allowed
+ if (vertxContext == null || toAttach == null) {
return Scope.noop();
}
+ // We don't allow to attach the OpenTelemetry Context to a Vert.x Context that is not a duplicate.
+ if (!isDuplicatedContext(vertxContext)) {
+ throw new IllegalArgumentException(
+ "The Vert.x Context to attach the OpenTelemetry Context must be a duplicated Context");
+ }
+
Context beforeAttach = getContext(vertxContext);
if (toAttach == beforeAttach) {
return Scope.noop();
}
- if (vertxContext != null) {
- vertxContext.putLocal(ACTIVE_CONTEXT, toAttach);
- return () -> {
- if (getContext(vertxContext) != toAttach) {
- log.warn("Context in storage not the expected context, Scope.close was not called correctly");
- }
- if (beforeAttach == null) {
- vertxContext.removeLocal(ACTIVE_CONTEXT);
- ((ContextInternal) vertxContext).unwrap().removeLocal(ACTIVE_CONTEXT);
- } else {
- vertxContext.putLocal(ACTIVE_CONTEXT, beforeAttach);
- }
- };
- }
+ vertxContext.putLocal(OTEL_CONTEXT, toAttach);
+
+ return () -> {
+ if (getContext(vertxContext) != toAttach) {
+ log.warn("Context in storage not the expected context, Scope.close was not called correctly");
+ }
- return Scope.noop();
+ if (beforeAttach == null) {
+ vertxContext.removeLocal(OTEL_CONTEXT);
+ } else {
+ vertxContext.putLocal(OTEL_CONTEXT, beforeAttach);
+ }
+ };
}
+ /**
+ * Gets the current OpenTelemetry Context from the current Vert.x Context if one exists or from the default
+ * ContextStorage. The current Vert.x Context must be a duplicated Context.
+ *
+ * @return the current OpenTelemetry Context or null.
+ */
@Override
public Context current() {
- return getContext(getVertxContext());
+ return Vertx.currentContext() != null ? getOrCreateDuplicatedContext(vertx).getLocal(OTEL_CONTEXT)
+ : DEFAULT_CONTEXT_STORAGE.current();
}
- private Context getContext(io.vertx.core.Context vertxContext) {
- return vertxContext != null ? vertxContext.getLocal(ACTIVE_CONTEXT) : null;
+ /**
+ * Gets the OpenTelemetry Context in a Vert.x Context. The Vert.x Context has to be a duplicate context.
+ *
+ * @param vertxContext a Vert.x Context.
+ * @return the OpenTelemetry Context if exists in the Vert.x Context or null.
+ */
+ public static Context getContext(io.vertx.core.Context vertxContext) {
+ return vertxContext != null && isDuplicatedContext(vertxContext) ? vertxContext.getLocal(OTEL_CONTEXT) : null;
}
- private io.vertx.core.Context getVertxContext() {
- return vertx.getOrCreateContext();
+ /**
+ * Gets the current duplicated context or a new duplicated context if a Vert.x Context exists. Multiple invocations
+ * of this method may return the same or different context. If the current context is a duplicate one, multiple
+ * invocations always return the same context. If the current context is not duplicated, a new instance is returned
+ * with each method invocation.
+ *
+ * @return a duplicated Vert.x Context or null.
+ */
+ private static io.vertx.core.Context getVertxContext() {
+ return Vertx.currentContext() != null ? getOrCreateDuplicatedContext(vertx) : null;
}
}
diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/OpenTelemetryClientFilter.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/OpenTelemetryClientFilter.java
index 7601457bf5aea..8be764b6ac3fd 100644
--- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/OpenTelemetryClientFilter.java
+++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/restclient/OpenTelemetryClientFilter.java
@@ -12,6 +12,7 @@
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.Provider;
@@ -28,6 +29,17 @@
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import io.quarkus.arc.Unremovable;
+/**
+ * A client filter for the JAX-RS Client and MicroProfile REST Client that records OpenTelemetry data.
+ *
+ * For the Resteasy Reactive Client, we skip the OpenTelemetry registration, since this can be handled by the
+ * {@link io.quarkus.opentelemetry.runtime.tracing.vertx.OpenTelemetryVertxTracer}. In theory, this wouldn't be an
+ * issue, because the OpenTelemetry Instrumenter detects two Client Span and merge both together, but they need to be
+ * executed with the same OpenTelemetry Context. Right now, the Reactive REST Client filters are executed outside the
+ * Vert.x Context, so we are unable to propagate the OpenTelemetry Context. This is also not a big issue, because the
+ * correct OpenTelemetry data will be populated in Vert.x. The only missing piece is the route name available in
+ * io.quarkus.resteasy.reactive.server.runtime.observability.ObservabilityHandler, which is not propagated to Vert.x.
+ */
@Unremovable
@Provider
public class OpenTelemetryClientFilter implements ClientRequestFilter, ClientResponseFilter {
@@ -60,6 +72,10 @@ public OpenTelemetryClientFilter(final OpenTelemetry openTelemetry) {
@Override
public void filter(final ClientRequestContext request) {
+ if (isReactiveClient(request)) {
+ return;
+ }
+
Context parentContext = Context.current();
if (instrumenter.shouldStart(parentContext, request)) {
Context spanContext = instrumenter.start(parentContext, request);
@@ -89,6 +105,10 @@ public void filter(final ClientRequestContext request, final ClientResponseConte
}
}
+ static boolean isReactiveClient(final ClientRequestContext request) {
+ return "Resteasy Reactive Client".equals(request.getHeaderString(HttpHeaders.USER_AGENT));
+ }
+
private static class ClientRequestContextTextMapSetter implements TextMapSetter {
@Override
public void set(final ClientRequestContext carrier, final String key, final String value) {
diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/OpenTelemetryVertxTracer.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/OpenTelemetryVertxTracer.java
index ca0593d8aba7c..d308c8f7aeace 100644
--- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/OpenTelemetryVertxTracer.java
+++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/OpenTelemetryVertxTracer.java
@@ -3,7 +3,6 @@
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_CLIENT_IP;
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_ROUTE;
import static io.quarkus.opentelemetry.runtime.OpenTelemetryConfig.INSTRUMENTATION_NAME;
-import static io.quarkus.opentelemetry.runtime.QuarkusContextStorage.ACTIVE_CONTEXT;
import java.net.URI;
import java.util.List;
@@ -27,6 +26,7 @@
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import io.quarkus.opentelemetry.runtime.QuarkusContextStorage;
+import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.Context;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
@@ -75,6 +75,7 @@ public OpenTelemetryVertxTracer(OpenTelemetry openTelemetry) {
@Override
public SpanOperation receiveRequest(
+ // The Vert.x context passed to use is already duplicated.
final Context context,
final SpanKind kind,
final TracingPolicy policy,
@@ -87,9 +88,9 @@ public SpanOperation receiveRequest(
return null;
}
- io.opentelemetry.context.Context parentContext = context.getLocal(ACTIVE_CONTEXT);
+ io.opentelemetry.context.Context parentContext = QuarkusContextStorage.getContext(context);
if (parentContext == null) {
- parentContext = io.opentelemetry.context.Context.root();
+ parentContext = io.opentelemetry.context.Context.current();
}
if (serverInstrumenter.shouldStart(parentContext, (HttpRequest) request)) {
@@ -103,6 +104,7 @@ public SpanOperation receiveRequest(
@Override
public void sendResponse(
+ // The Vert.x context passed to use is already duplicated.
final Context context,
final R response,
final SpanOperation spanOperation,
@@ -132,6 +134,7 @@ public void sendResponse(
@Override
public SpanOperation sendRequest(
+ // This context is not duplicated, so we need to do it.
final Context context,
final SpanKind kind,
final TracingPolicy policy,
@@ -144,16 +147,17 @@ public SpanOperation sendRequest(
return null;
}
- io.opentelemetry.context.Context parentContext = context.getLocal(ACTIVE_CONTEXT);
+ io.opentelemetry.context.Context parentContext = QuarkusContextStorage.getContext(context);
if (parentContext == null) {
- parentContext = io.opentelemetry.context.Context.root();
+ parentContext = io.opentelemetry.context.Context.current();
}
if (clientInstrumenter.shouldStart(parentContext, (HttpRequest) request)) {
io.opentelemetry.context.Context spanContext = clientInstrumenter.start(parentContext,
WriteHeadersHttpRequest.request((HttpRequest) request, headers));
- Scope scope = QuarkusContextStorage.INSTANCE.attach(context, spanContext);
- return SpanOperation.span(context, (HttpRequest) request, spanContext, scope);
+ Context duplicatedContext = VertxContext.getOrCreateDuplicatedContext(context);
+ Scope scope = QuarkusContextStorage.INSTANCE.attach(duplicatedContext, spanContext);
+ return SpanOperation.span(duplicatedContext, (HttpRequest) request, spanContext, scope);
}
return null;
@@ -161,6 +165,8 @@ public SpanOperation sendRequest(
@Override
public void receiveResponse(
+ // This context is not duplicated, so we need to do it, but we can't duplicate it again because it was already done in
+ // io.quarkus.opentelemetry.runtime.tracing.vertx.OpenTelemetryVertxTracer.sendRequest, but we don't use it so it should be ok.
final Context context,
final R response,
final SpanOperation spanOperation,
diff --git a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/context/DuplicatedContextTest.java b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/context/DuplicatedContextTest.java
index 117bcb7064946..a7ef899529589 100644
--- a/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/context/DuplicatedContextTest.java
+++ b/extensions/reactive-routes/deployment/src/test/java/io/quarkus/vertx/web/context/DuplicatedContextTest.java
@@ -18,6 +18,8 @@
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.vertx.web.Route;
import io.smallrye.common.annotation.Blocking;
+import io.smallrye.common.vertx.ContextLocals;
+import io.smallrye.common.vertx.VertxContext;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import io.vertx.core.Context;
@@ -25,8 +27,6 @@
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpMethod;
-import io.vertx.core.impl.EventLoopContext;
-import io.vertx.core.impl.WorkerContext;
import io.vertx.ext.web.RoutingContext;
/**
@@ -87,14 +87,13 @@ void getBlocking(RoutingContext ctx) {
private void process(RoutingContext ctx) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals. get("key").orElse(null);
Assertions.assertNull(val);
String id = ctx.pathParam("id");
- context.putLocal("key", id);
+ ContextLocals.put("key", id);
vertx.createHttpClient().request(HttpMethod.GET, 8081, "localhost", "/hey")
.compose(request -> request.end().compose(x -> request.response()))
@@ -102,9 +101,9 @@ private void process(RoutingContext ctx) {
.map(Buffer::toString)
.onSuccess(msg -> {
Assertions.assertEquals("hey!", msg);
- Assertions.assertEquals(id, Vertx.currentContext().getLocal("key"));
+ Assertions.assertEquals(id, ContextLocals. get("key").orElseThrow());
Assertions.assertSame(Vertx.currentContext(), context);
- ctx.response().end("OK-" + context.getLocal("key"));
+ ctx.response().end("OK-" + ContextLocals.get("key").orElseThrow());
});
}
diff --git a/extensions/vertx-http/runtime/pom.xml b/extensions/vertx-http/runtime/pom.xml
index 3b6ab58554b66..a5326ef98b710 100644
--- a/extensions/vertx-http/runtime/pom.xml
+++ b/extensions/vertx-http/runtime/pom.xml
@@ -25,6 +25,10 @@
io.quarkus
quarkus-mutiny
+
+ io.smallrye.common
+ smallrye-common-vertx-context
+
io.quarkus
quarkus-vertx-http-dev-console-runtime-spi
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
index 18645cc249a72..929b1088030ff 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
@@ -73,6 +73,7 @@
import io.quarkus.vertx.http.runtime.filters.accesslog.AccessLogReceiver;
import io.quarkus.vertx.http.runtime.filters.accesslog.DefaultAccessLogReceiver;
import io.quarkus.vertx.http.runtime.filters.accesslog.JBossLoggingAccessLogReceiver;
+import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
@@ -92,6 +93,7 @@
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.impl.Http1xServerConnection;
+import io.vertx.core.impl.ContextInternal;
import io.vertx.core.impl.EventLoopContext;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.net.JdkSSLEngineOptions;
@@ -1207,7 +1209,7 @@ public void initChannel(VirtualChannel ch) throws Exception {
VertxHandler handler = VertxHandler.create(chctx -> {
Http1xServerConnection conn = new Http1xServerConnection(
- () -> context,
+ () -> (ContextInternal) VertxContext.getOrCreateDuplicatedContext(context),
null,
new HttpServerOptions(),
chctx,
diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java
index 2d716efbe07c7..97f5961474fb1 100644
--- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java
+++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/core/deployment/VertxCoreProcessor.java
@@ -21,6 +21,10 @@
import org.jboss.logging.Logger;
import org.jboss.logmanager.Level;
import org.jboss.logmanager.LogManager;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
@@ -28,6 +32,7 @@
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ContextHandlerBuildItem;
import io.quarkus.deployment.builditem.ExecutorBuildItem;
@@ -40,8 +45,10 @@
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.logging.LogCleanupFilterBuildItem;
+import io.quarkus.gizmo.Gizmo;
import io.quarkus.netty.deployment.EventLoopSupplierBuildItem;
import io.quarkus.vertx.core.runtime.VertxCoreRecorder;
+import io.quarkus.vertx.core.runtime.VertxLocalsHelper;
import io.quarkus.vertx.core.runtime.VertxLogDelegateFactory;
import io.quarkus.vertx.core.runtime.config.VertxConfiguration;
import io.vertx.core.AbstractVerticle;
@@ -80,6 +87,80 @@ LogCleanupFilterBuildItem cleanupVertxWarnings() {
return new LogCleanupFilterBuildItem("io.vertx.core.impl.ContextImpl", "You have disabled TCCL checks");
}
+ @BuildStep
+ BytecodeTransformerBuildItem overrideContextToAddSafeGuards() {
+ return new BytecodeTransformerBuildItem("io.vertx.core.impl.AbstractContext",
+ (className, classVisitor) -> new ClassVisitor(Gizmo.ASM_API_VERSION, classVisitor) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
+ String[] exceptions) {
+ MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+
+ if (name.equals("get") || name.equals("put") || name.equals("remove")) {
+ return new MethodVisitor(Gizmo.ASM_API_VERSION, visitor) {
+ @Override
+ public void visitCode() {
+ super.visitCode();
+ visitMethodInsn(Opcodes.INVOKESTATIC,
+ VertxLocalsHelper.class.getName().replace(".", "/"), "throwOnRootContextAccess",
+ "()V", false);
+ }
+ };
+ }
+
+ if (name.equals("getLocal")) {
+ return new MethodVisitor(Gizmo.ASM_API_VERSION, visitor) {
+ @Override
+ public void visitCode() {
+ super.visitCode();
+ visitVarInsn(Opcodes.ALOAD, 0); // this
+ visitVarInsn(Opcodes.ALOAD, 1); // first param (object)
+ visitMethodInsn(Opcodes.INVOKESTATIC,
+ VertxLocalsHelper.class.getName().replace(".", "/"), "getLocal",
+ "(Lio/vertx/core/impl/ContextInternal;Ljava/lang/Object;)Ljava/lang/Object;",
+ false);
+ visitInsn(Opcodes.ARETURN);
+ }
+ };
+ }
+
+ if (name.equals("putLocal")) {
+ return new MethodVisitor(Gizmo.ASM_API_VERSION, visitor) {
+ @Override
+ public void visitCode() {
+ super.visitCode();
+ visitVarInsn(Opcodes.ALOAD, 0); // this
+ visitVarInsn(Opcodes.ALOAD, 1); // first param (object)
+ visitVarInsn(Opcodes.ALOAD, 2); // second param (object)
+ visitMethodInsn(Opcodes.INVOKESTATIC,
+ VertxLocalsHelper.class.getName().replace(".", "/"), "putLocal",
+ "(Lio/vertx/core/impl/ContextInternal;Ljava/lang/Object;Ljava/lang/Object;)V",
+ false);
+ visitInsn(Opcodes.RETURN);
+ }
+ };
+ }
+
+ if (name.equals("removeLocal")) {
+ return new MethodVisitor(Gizmo.ASM_API_VERSION, visitor) {
+ @Override
+ public void visitCode() {
+ super.visitCode();
+ visitVarInsn(Opcodes.ALOAD, 0); // this
+ visitVarInsn(Opcodes.ALOAD, 1); // first param (object)
+ visitMethodInsn(Opcodes.INVOKESTATIC,
+ VertxLocalsHelper.class.getName().replace(".", "/"), "removeLocal",
+ "(Lio/vertx/core/impl/ContextInternal;Ljava/lang/Object;)Z", false);
+ visitInsn(Type.getType(Boolean.TYPE).getOpcode(Opcodes.IRETURN));
+ }
+ };
+ }
+
+ return visitor;
+ }
+ });
+ }
+
@BuildStep
LogCategoryBuildItem preventLoggerContention() {
//Prevent the Logging warning about the TCCL checks being disabled to be logged;
diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/DuplicatedContextTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/DuplicatedContextTest.java
index 52b320e66db31..b1e615b69dfba 100644
--- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/DuplicatedContextTest.java
+++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/DuplicatedContextTest.java
@@ -20,6 +20,8 @@
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.common.annotation.Blocking;
+import io.smallrye.common.vertx.ContextLocals;
+import io.smallrye.common.vertx.VertxContext;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import io.vertx.core.Context;
@@ -27,8 +29,6 @@
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpMethod;
-import io.vertx.core.impl.EventLoopContext;
-import io.vertx.core.impl.WorkerContext;
import io.vertx.mutiny.core.eventbus.EventBus;
import io.vertx.mutiny.core.eventbus.Message;
@@ -152,10 +152,9 @@ Uni receive(String data) {
private Uni process(String id) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals.get("key", null);
Assertions.assertNull(val);
context.putLocal("key", id);
@@ -167,9 +166,9 @@ private Uni process(String id) {
.map(Buffer::toString)
.map(msg -> {
Assertions.assertEquals("hey!", msg);
- Assertions.assertEquals(id, Vertx.currentContext().getLocal("key"));
+ Assertions.assertEquals(id, ContextLocals.get("key", null));
Assertions.assertSame(Vertx.currentContext(), context);
- return "OK-" + context.getLocal("key");
+ return "OK-" + ContextLocals.get("key", null);
}).toCompletionStage());
}
@@ -183,10 +182,9 @@ String receiveBlocking(String data) {
@ConsumeEvent(value = "context-send")
public void consumeSend(String s) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals.get("key", null);
Assertions.assertNull(val);
context.putLocal("key", s);
@@ -196,10 +194,9 @@ public void consumeSend(String s) {
@ConsumeEvent(value = "context-send-blocking")
public void consumeSendBlocking(String s) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals.get("key", null);
Assertions.assertNull(val);
context.putLocal("key", s);
@@ -209,10 +206,9 @@ public void consumeSendBlocking(String s) {
@ConsumeEvent(value = "context-publish")
public void consumePublish1(String s) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals.get("key", null);
Assertions.assertNull(val);
context.putLocal("key", s);
@@ -222,10 +218,9 @@ public void consumePublish1(String s) {
@ConsumeEvent(value = "context-publish")
public void consumePublish2(String s) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals.get("key", null);
Assertions.assertNull(val);
context.putLocal("key", s);
@@ -236,10 +231,9 @@ public void consumePublish2(String s) {
@Blocking
public void consumePublishBlocking1(String s) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals.get("key", null);
Assertions.assertNull(val);
context.putLocal("key", s);
@@ -250,10 +244,9 @@ public void consumePublishBlocking1(String s) {
@Blocking
public void consumePublishBlocking2(String s) {
Context context = Vertx.currentContext();
- Assertions.assertFalse(context instanceof EventLoopContext);
- Assertions.assertFalse(context instanceof WorkerContext);
+ Assertions.assertTrue(VertxContext.isOnDuplicatedContext());
- String val = context.getLocal("key");
+ String val = ContextLocals.get("key", null);
Assertions.assertNull(val);
context.putLocal("key", s);
diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/locals/LocalContextAccessTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/locals/LocalContextAccessTest.java
new file mode 100644
index 0000000000000..35c465a2d7f2c
--- /dev/null
+++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/locals/LocalContextAccessTest.java
@@ -0,0 +1,188 @@
+package io.quarkus.vertx.locals;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+
+import org.assertj.core.api.Assertions;
+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.test.QuarkusUnitTest;
+import io.smallrye.common.vertx.VertxContext;
+import io.vertx.core.Context;
+import io.vertx.core.Vertx;
+import io.vertx.core.impl.ContextInternal;
+
+public class LocalContextAccessTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(() -> ShrinkWrap
+ .create(JavaArchive.class).addClasses(BeanAccessingContext.class));
+
+ @Inject
+ Vertx vertx;
+
+ @Inject
+ BeanAccessingContext bean;
+
+ @Test
+ public void testGlobalAccessFromEventLoop() throws ExecutionException, InterruptedException, TimeoutException {
+ Context context = vertx.getOrCreateContext();
+ CompletableFuture get = new CompletableFuture<>();
+ CompletableFuture put = new CompletableFuture<>();
+ CompletableFuture remove = new CompletableFuture<>();
+
+ context.runOnContext(x -> {
+ try {
+ bean.getGlobal();
+ get.completeExceptionally(new Exception("Exception expected as using get is forbidden"));
+ } catch (Exception e) {
+ get.complete(e);
+ }
+ });
+ Throwable t = get.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ Assertions.assertThat(t).isInstanceOf(UnsupportedOperationException.class).hasMessageContaining("Context.get()");
+
+ context.runOnContext(x -> {
+ try {
+ bean.putGlobal();
+ put.completeExceptionally(new Exception("Exception expected as using put is forbidden"));
+ } catch (Exception e) {
+ put.complete(e);
+ }
+ });
+ t = put.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ Assertions.assertThat(t).isInstanceOf(UnsupportedOperationException.class).hasMessageContaining("Context.put()");
+
+ context.runOnContext(x -> {
+ try {
+ bean.removeGlobal();
+ remove.completeExceptionally(new Exception("Exception expected as using remove is forbidden"));
+ } catch (Exception e) {
+ remove.complete(e);
+ }
+ });
+ t = remove.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ Assertions.assertThat(t).isInstanceOf(UnsupportedOperationException.class).hasMessageContaining("Context.remove()");
+ }
+
+ @Test
+ public void testLocalAccessFromEventLoop() throws ExecutionException, InterruptedException, TimeoutException {
+ Context context = vertx.getOrCreateContext();
+ CompletableFuture get = new CompletableFuture<>();
+ CompletableFuture put = new CompletableFuture<>();
+ CompletableFuture remove = new CompletableFuture<>();
+
+ context.runOnContext(x -> {
+ try {
+ bean.getLocal();
+ get.completeExceptionally(new Exception("Exception expected as using get is forbidden"));
+ } catch (Exception e) {
+ get.complete(e);
+ }
+ });
+ Throwable t = get.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ Assertions.assertThat(t).isInstanceOf(UnsupportedOperationException.class).hasMessageContaining("Context.getLocal()");
+
+ context.runOnContext(x -> {
+ try {
+ bean.putLocal();
+ put.completeExceptionally(new Exception("Exception expected as using put is forbidden"));
+ } catch (Exception e) {
+ put.complete(e);
+ }
+ });
+ t = put.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ Assertions.assertThat(t).isInstanceOf(UnsupportedOperationException.class).hasMessageContaining("Context.putLocal()");
+
+ context.runOnContext(x -> {
+ try {
+ bean.removeLocal();
+ remove.completeExceptionally(new Exception("Exception expected as using remove is forbidden"));
+ } catch (Exception e) {
+ remove.complete(e);
+ }
+ });
+ t = remove.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ Assertions.assertThat(t).isInstanceOf(UnsupportedOperationException.class)
+ .hasMessageContaining("Context.removeLocal()");
+ }
+
+ @Test
+ public void testLocalAccessFromDuplicatedContext() throws ExecutionException, InterruptedException, TimeoutException {
+ ContextInternal context = (ContextInternal) vertx.getOrCreateContext();
+ CompletableFuture get = new CompletableFuture<>();
+ CompletableFuture put = new CompletableFuture<>();
+ CompletableFuture remove = new CompletableFuture<>();
+
+ Context local = VertxContext.getOrCreateDuplicatedContext(context);
+
+ local.runOnContext(x -> {
+ try {
+ bean.putLocal();
+ put.complete(null);
+ } catch (Exception e) {
+ get.completeExceptionally(e);
+ }
+ });
+ put.toCompletableFuture().get(5, TimeUnit.SECONDS);
+
+ local.runOnContext(x -> {
+ try {
+ Assertions.assertThat(bean.getLocal()).isEqualTo("bar");
+ get.complete(null);
+ } catch (Exception e) {
+ get.completeExceptionally(e);
+ }
+ });
+ get.toCompletableFuture().get(5, TimeUnit.SECONDS);
+
+ local.runOnContext(x -> {
+ try {
+ Assertions.assertThat(bean.removeLocal()).isTrue();
+ remove.complete(null);
+ } catch (Exception e) {
+ remove.completeExceptionally(e);
+ }
+ });
+ remove.toCompletableFuture().get(5, TimeUnit.SECONDS);
+ }
+
+ @ApplicationScoped
+ public static class BeanAccessingContext {
+
+ public String getGlobal() {
+ return Vertx.currentContext().get("foo");
+ }
+
+ public void putGlobal() {
+ Vertx.currentContext().put("foo", "bar");
+ }
+
+ public void removeGlobal() {
+ Vertx.currentContext().remove("foo");
+ }
+
+ public String getLocal() {
+ return Vertx.currentContext().getLocal("foo");
+ }
+
+ public void putLocal() {
+ Vertx.currentContext().putLocal("foo", "bar");
+ }
+
+ public boolean removeLocal() {
+ return Vertx.currentContext().removeLocal("foo");
+ }
+
+ }
+
+}
diff --git a/extensions/vertx/runtime/pom.xml b/extensions/vertx/runtime/pom.xml
index 2ca2d292dcb5a..5970735688cf6 100644
--- a/extensions/vertx/runtime/pom.xml
+++ b/extensions/vertx/runtime/pom.xml
@@ -34,6 +34,10 @@
io.smallrye.common
smallrye-common-annotation
+
+ io.smallrye.common
+ smallrye-common-vertx-context
+
io.quarkus
quarkus-mutiny
diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxLocalsHelper.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxLocalsHelper.java
new file mode 100644
index 0000000000000..6642f6bd1b80f
--- /dev/null
+++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxLocalsHelper.java
@@ -0,0 +1,48 @@
+package io.quarkus.vertx.core.runtime;
+
+import io.smallrye.common.vertx.VertxContext;
+import io.vertx.core.impl.ContextInternal;
+
+public class VertxLocalsHelper {
+
+ private static final String ILLEGAL_ACCESS_TO_CONTEXT = "Access to Context.put(), Context.get() and Context.remove() " +
+ "are forbidden as it can leak data between unrelated processing. Use Context.putLocal(), Context.getLocal() " +
+ "and Context.removeLocal() instead. Note that these methods can only be used from a 'duplicated' Context, " +
+ "and so may not be available everywhere.";
+
+ private static final String ILLEGAL_ACCESS_TO_LOCAL_CONTEXT = "Access to Context.putLocal(), Context.getLocal() and" +
+ " Context.removeLocal() are forbidden from a 'root' context as it can leak data between unrelated processing." +
+ " Make sure the method runs on a 'duplicated' (local) Context";
+
+ public static void throwOnRootContextAccess() {
+ throw new UnsupportedOperationException(ILLEGAL_ACCESS_TO_CONTEXT);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T getLocal(ContextInternal context, Object key) {
+ if (VertxContext.isDuplicatedContext(context)) {
+ // We are on a duplicated context, allow accessing the locals
+ return (T) context.localContextData().get(key);
+ } else {
+ throw new UnsupportedOperationException(ILLEGAL_ACCESS_TO_LOCAL_CONTEXT);
+ }
+ }
+
+ public static void putLocal(ContextInternal context, Object key, Object value) {
+ if (VertxContext.isDuplicatedContext(context)) {
+ // We are on a duplicated context, allow accessing the locals
+ context.localContextData().put(key, value);
+ } else {
+ throw new UnsupportedOperationException(ILLEGAL_ACCESS_TO_LOCAL_CONTEXT);
+ }
+ }
+
+ public static boolean removeLocal(ContextInternal context, Object key) {
+ if (VertxContext.isDuplicatedContext(context)) {
+ // We are on a duplicated context, allow accessing the locals
+ return context.localContextData().remove(key) != null;
+ } else {
+ throw new UnsupportedOperationException(ILLEGAL_ACCESS_TO_LOCAL_CONTEXT);
+ }
+ }
+}
diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/context/VertxContextSafetyToggle.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/context/VertxContextSafetyToggle.java
index 67acf584e900c..f4203291f218b 100644
--- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/context/VertxContextSafetyToggle.java
+++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/context/VertxContextSafetyToggle.java
@@ -1,5 +1,6 @@
package io.quarkus.vertx.core.runtime.context;
+import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
@@ -64,6 +65,10 @@ public static void validateContextIfExists(final String errorMessageOnVeto, fina
}
private static void checkIsSafe(final Context context, final String errorMessageOnVeto, final String errorMessageOnDoubt) {
+ if (!VertxContext.isDuplicatedContext(context)) {
+ throw new IllegalStateException(
+ "Can't get the context safety flag: the current context is not a duplicated context");
+ }
final Object safeFlag = context.getLocal(ACCESS_TOGGLE_KEY);
if (safeFlag == Boolean.TRUE) {
return;
@@ -88,6 +93,9 @@ public static void setCurrentContextSafe(boolean safe) {
final io.vertx.core.Context context = Vertx.currentContext();
if (context == null) {
throw new IllegalStateException("Can't set the context safety flag: no Vert.x context found");
+ } else if (!VertxContext.isDuplicatedContext(context)) {
+ throw new IllegalStateException(
+ "Can't set the context safety flag: the current context is not a duplicated context");
} else {
context.putLocal(ACCESS_TOGGLE_KEY, Boolean.valueOf(safe));
}
diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java
index 3b3c8e7f3de7a..4802063fbf9c0 100644
--- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java
+++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxRecorder.java
@@ -17,6 +17,7 @@
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ProfileManager;
import io.quarkus.vertx.ConsumeEvent;
+import io.smallrye.common.vertx.VertxContext;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Handler;
@@ -98,7 +99,7 @@ public void handle(Void x) {
public void handle(Message