Skip to content

Commit 9552899

Browse files
committed
Fix Jetty 12 HTTP server metrics collection without virtual threads
After upgrading to Dropwizard 5 (which uses Jetty 12), HTTP server metrics were not being collected or exposed correctly when virtual threads were disabled (enableVirtualThreads=false). The metrics appeared correctly only when virtual threads were enabled. Root Cause: The Jetty12ServerInstrumentation was ending spans in two places: 1. In onMethodExit when the handle() method completed 2. In HttpStream callbacks (succeeded/failed) for request completion When virtual threads were disabled, handle() often completed synchronously BEFORE the HttpStream callbacks fired. This caused the span to end prematurely in onMethodExit, and metrics were not properly captured. The HttpStream callbacks would then try to end an already-ended span. When virtual threads were enabled, the asynchronous nature ensured callbacks fired before method exit, so metrics were captured correctly. Solution: 1. Modified Jetty12ServerInstrumentation.HandlerAdvice.end() to NOT end the span in onMethodExit (except when there's an exception). The span is now ended exclusively by the HttpStream completion callbacks. 2. Added AtomicBoolean in Jetty12Helper.start() to ensure the span ends exactly once, preventing double-ending issues regardless of callback order. This ensures HTTP metrics (request counts, latency, body size, per-endpoint and per-status-code metrics) are collected correctly whether virtual threads are enabled or disabled. Fixes the issue where Dropwizard 5 applications with enableVirtualThreads=false would not report HTTP server metrics.
1 parent ad6cc1b commit 9552899

File tree

2 files changed

+20
-3
lines changed

2 files changed

+20
-3
lines changed

instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12Helper.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1010
import io.opentelemetry.javaagent.bootstrap.servlet.AppServerBridge;
1111
import io.opentelemetry.javaagent.bootstrap.servlet.ServletAsyncContext;
12+
import java.util.concurrent.atomic.AtomicBoolean;
1213
import javax.annotation.Nullable;
1314
import org.eclipse.jetty.server.HttpStream;
1415
import org.eclipse.jetty.server.Request;
@@ -27,21 +28,33 @@ public boolean shouldStart(Context parentContext, Request request) {
2728

2829
public Context start(Context parentContext, Request request, Response response) {
2930
Context context = instrumenter.start(parentContext, request);
30-
request.addFailureListener(throwable -> end(context, request, response, throwable));
31+
// Use AtomicBoolean to ensure the span ends exactly once
32+
AtomicBoolean spanEnded = new AtomicBoolean(false);
33+
34+
request.addFailureListener(
35+
throwable -> {
36+
if (spanEnded.compareAndSet(false, true)) {
37+
end(context, request, response, throwable);
38+
}
39+
});
3140
// detect request completion
3241
// https://github.com/jetty/jetty.project/blob/52d94174e2c7a6e794c6377dcf9cd3ed0b9e1806/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/EventsHandler.java#L75
3342
request.addHttpStreamWrapper(
3443
stream ->
3544
new HttpStream.Wrapper(stream) {
3645
@Override
3746
public void succeeded() {
38-
end(context, request, response, null);
47+
if (spanEnded.compareAndSet(false, true)) {
48+
end(context, request, response, null);
49+
}
3950
super.succeeded();
4051
}
4152

4253
@Override
4354
public void failed(Throwable throwable) {
44-
end(context, request, response, throwable);
55+
if (spanEnded.compareAndSet(false, true)) {
56+
end(context, request, response, throwable);
57+
}
4558
super.failed(throwable);
4659
}
4760
});

instrumentation/jetty/jetty-12.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jetty/v12_0/Jetty12ServerInstrumentation.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public static AdviceScope start(Object source, Request request, Response respons
6767

6868
public void end(Request request, Response response, @Nullable Throwable throwable) {
6969
scope.close();
70+
// Don't end the span here - it will be ended by the HttpStream callbacks
71+
// registered in Jetty12Helper.start(). This ensures metrics are captured
72+
// correctly regardless of whether virtual threads are enabled.
73+
// Only end immediately if there's an exception, as the callbacks may not fire.
7074
if (throwable != null) {
7175
helper().end(context, request, response, throwable);
7276
}

0 commit comments

Comments
 (0)