Skip to content

Commit 97f9933

Browse files
committed
fix(vertx-web): finish vertx.route-handler via RoutingContext.addEndHandler fallback
Vert.x's `Http1xServerResponse.end(Buffer, PromiseInternal)` invokes the registered `endHandler` only when `closed == false` at the moment the response body has been written. In synthetic transports such as quarkus-amazon-lambda-rest's `VirtualClientConnection` (in-memory Netty channel) the writes and the connection close happen synchronously inside `responseComplete()`, so by the time the `!closed` guard runs `closed` is already `true` and `endHandler` is silently skipped. Symptom: `RouteHandlerWrapper` starts a `vertx.route-handler` span for every route in the chain (e.g. Quarkus's AuthenticationHandler) but `EndHandlerWrapper.handle` is never called, so the span is never finished. The span dies in PendingTrace and is not enqueued on the writer. All children parented to that span (`jakarta-rs.request`, `netty.client.request`, downstream `aws.http`/`aws.apigateway` inferred spans) end up orphaned in the trace UI. Fix: also register a finish via `RoutingContext.addEndHandler`, which fires on routing-context completion regardless of underlying connection state and on both success and failure. Both paths funnel through a shared idempotent `finishHandlerSpan` so the second one to fire on real-network transports is a no-op. Verified end-to-end against a Quarkus 3.15.4 / Java 21 Lambda chain (caller -> netty.client.request -> callee) on Datadog Lambda Extension v96. Pre-fix: 5/5 invocations Started, 0/5 Finished. Post-fix: 5/5 Started, 5/5 Finished, single connected trace tree in the UI. Refs: SLES-2837
1 parent d73cdc9 commit 97f9933

2 files changed

Lines changed: 24 additions & 9 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package datadog.trace.instrumentation.vertx_4_0.server;
22

3-
import static datadog.trace.instrumentation.vertx_4_0.server.RouteHandlerWrapper.HANDLER_SPAN_CONTEXT_KEY;
4-
import static datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator.DECORATE;
5-
6-
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
73
import io.vertx.core.Handler;
84
import io.vertx.ext.web.RoutingContext;
95

@@ -18,16 +14,12 @@ public class EndHandlerWrapper implements Handler<Void> {
1814

1915
@Override
2016
public void handle(final Void event) {
21-
AgentSpan span = routingContext.get(HANDLER_SPAN_CONTEXT_KEY);
2217
try {
2318
if (actual != null) {
2419
actual.handle(event);
2520
}
2621
} finally {
27-
if (span != null) {
28-
DECORATE.onResponse(span, routingContext.response());
29-
span.finish();
30-
}
22+
RouteHandlerWrapper.finishHandlerSpan(routingContext);
3123
}
3224
}
3325
}

dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RouteHandlerWrapper.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ public void handle(final RoutingContext routingContext) {
4444
routingContext.put(HANDLER_SPAN_CONTEXT_KEY, span);
4545

4646
routingContext.response().endHandler(new EndHandlerWrapper(routingContext));
47+
// Fallback finish path: HttpServerResponse.endHandler is silently skipped
48+
// by Vert.x's Http1xServerResponse.end() when the underlying connection
49+
// has already closed (Http1xServerResponse#end gates `endHandler.handle()`
50+
// behind `!closed`). This happens in synthetic transports such as
51+
// quarkus-amazon-lambda-rest's virtual Netty channel, where writes and
52+
// close are synchronous in-memory, leaving the route-handler span unfinished
53+
// and orphaning all jakarta-rs.request / aws.http child spans in the trace.
54+
// RoutingContext#addEndHandler fires on routing-context completion regardless
55+
// of underlying connection state and on both success and failure.
56+
routingContext.addEndHandler(ar -> finishHandlerSpan(routingContext));
4757
DECORATE.afterStart(span);
4858
span.setResourceName(DECORATE.className(actual.getClass()));
4959
}
@@ -60,6 +70,19 @@ public void handle(final RoutingContext routingContext) {
6070
}
6171
}
6272

73+
// Idempotently finish the route-handler span. Both EndHandlerWrapper (the
74+
// response.endHandler path) and the routingContext.addEndHandler fallback may call
75+
// this; the first one to win clears HANDLER_SPAN_CONTEXT_KEY so the second is a no-op.
76+
static void finishHandlerSpan(final RoutingContext routingContext) {
77+
final AgentSpan span = routingContext.get(HANDLER_SPAN_CONTEXT_KEY);
78+
if (span == null) {
79+
return;
80+
}
81+
routingContext.put(HANDLER_SPAN_CONTEXT_KEY, null);
82+
DECORATE.onResponse(span, routingContext.response());
83+
span.finish();
84+
}
85+
6386
private void setRoute(RoutingContext routingContext) {
6487
final AgentSpan parentSpan = routingContext.get(PARENT_SPAN_CONTEXT_KEY);
6588
if (parentSpan == null) {

0 commit comments

Comments
 (0)