From 977fe1c3346dfed297bab64b6d586d38aa7592b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vandon?= Date: Thu, 2 Jul 2026 10:32:05 +0200 Subject: [PATCH 1/3] finish spans on blocking exceptions --- .../instrumentation/vertx_4_0/server/RouteHandlerWrapper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java index c34fb32cdfb..daeade4b13d 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java @@ -8,6 +8,7 @@ import static datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator.DECORATE; import static datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator.INSTRUMENTATION_NAME; +import datadog.appsec.api.blocking.BlockingException; import datadog.context.ContextScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.Tags; @@ -65,6 +66,9 @@ public void handle(final RoutingContext routingContext) { actual.handle(routingContext); } catch (final Throwable t) { DECORATE.onError(span, t); + if (t instanceof BlockingException) { + finishHandlerSpan(routingContext); + } throw t; } } From 0dc5ad42409cd4a70ed37ed8319931921e99fe72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vandon?= Date: Thu, 2 Jul 2026 11:58:24 +0200 Subject: [PATCH 2/3] add test --- .../server/VertxHttpServerForkedTest.groovy | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 8a9372dd9e7..0b7f25cb445 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -1,5 +1,6 @@ package server +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_JSON import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.LOGIN @@ -16,6 +17,8 @@ import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator import datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator import io.vertx.core.AbstractVerticle import io.vertx.core.Vertx +import okhttp3.MediaType +import okhttp3.RequestBody class VertxHttpServerForkedTest extends HttpServerTest { @Override @@ -181,4 +184,26 @@ class VertxHttpServerWorkerForkedTest extends VertxHttpServerForkedTest { HttpServer server() { return new VertxServer(verticle(), routerBasePath(), true) } + + def 'test blocking of JSON request body finishes route handler span'() { + setup: + // VertxTestServer handles BODY_JSON by calling ctx.body().asJsonObject(). + // The IG_BODY_CONVERTED_HEADER is consumed by HttpServerTest's AppSec test callback, which + // returns a RequestBlockingAction from requestBodyProcessed() when that JSON body is converted. + def request = request( + BODY_JSON, 'POST', + RequestBody.create(MediaType.get('application/json'), '{"a": "x"}')) + .header(IG_BODY_CONVERTED_HEADER, 'true') + .build() + + when: + def response = client.newCall(request).execute() + + then: + response.code() == 413 + response.body().charStream().text.contains('"title":"You\'ve been blocked"') + !handlerRan + // The client receiving a 413 only proves the blocking response was committed. We want to make sure that the BlockingException that is now thrown after version 5.1 does now abort the worker route handler before the vertx.route-handler span has been finished (which would leave it dangling) + TEST_WRITER.waitForTraces(1) + } } From ebbd5e1486caf33bf5db88c464a1decb08f2df55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vandon?= Date: Thu, 2 Jul 2026 14:56:46 +0200 Subject: [PATCH 3/3] comments & fix for other type --- .../server/HttpServerResponseEndHandlerInstrumentation.java | 4 +++- .../instrumentation/vertx_4_0/server/RouteHandlerWrapper.java | 3 +++ .../src/test/groovy/server/VertxHttpServerForkedTest.groovy | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/HttpServerResponseEndHandlerInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/HttpServerResponseEndHandlerInstrumentation.java index 5cfdf5767b2..d91e12e2835 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/HttpServerResponseEndHandlerInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/HttpServerResponseEndHandlerInstrumentation.java @@ -34,7 +34,9 @@ public String[] helperClassNames() { @Override public String[] knownMatchingTypes() { return new String[] { - "io.vertx.core.http.impl.Http1xServerResponse", "io.vertx.core.http.impl.Http2ServerResponse " + "io.vertx.core.http.impl.Http1xServerResponse", + "io.vertx.core.http.impl.Http2ServerResponse", + "io.vertx.core.http.impl.HttpServerResponseImpl" // when v >= 5.1 }; } diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java index daeade4b13d..70aaa687dde 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java @@ -67,6 +67,9 @@ public void handle(final RoutingContext routingContext) { } catch (final Throwable t) { DECORATE.onError(span, t); if (t instanceof BlockingException) { + // AppSec uses BlockingException as control flow after committing a blocking response + // from advice such as WafPublishingBodyHandler and RoutingContextJsonAdvice. Finish + // immediately because that abort path may bypass Vert.x response/routing end callbacks. finishHandlerSpan(routingContext); } throw t; diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 0b7f25cb445..2be39db6f4a 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -203,7 +203,9 @@ class VertxHttpServerWorkerForkedTest extends VertxHttpServerForkedTest { response.code() == 413 response.body().charStream().text.contains('"title":"You\'ve been blocked"') !handlerRan - // The client receiving a 413 only proves the blocking response was committed. We want to make sure that the BlockingException that is now thrown after version 5.1 does now abort the worker route handler before the vertx.route-handler span has been finished (which would leave it dangling) + // The client receiving a 413 only proves the blocking response was committed. + // We want to make sure that a BlockingException does now abort the worker route handler + // before the vertx.route-handler span has been finished (which would leave it dangling) TEST_WRITER.waitForTraces(1) } }