Skip to content

Commit bac29f7

Browse files
committed
fix: corrects h2 handling and cleans up otel Context currency when executing Promises
1 parent e1cfd6b commit bac29f7

8 files changed

Lines changed: 49 additions & 59 deletions

File tree

instrumentation/finagle-http-23.11/javaagent/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies {
3636

3737
implementation(project(":instrumentation:netty:netty-4.1:javaagent"))
3838
implementation(project(":instrumentation:netty:netty-4.1:library"))
39+
implementation(project(":instrumentation:netty:netty-common-4.0:javaagent"))
3940
implementation(project(":instrumentation:netty:netty-common-4.0:library"))
4041
}
4142

instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/GenStreamingServerDispatcherInstrumentation.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ public static Scope methodEnter(@Advice.Argument(0) Object req) {
4848
if (req instanceof Request) {
4949
// practically this will always be a Request, from HttpServerDispatcher
5050
Request request = (Request) req;
51+
// if this is null, there's a bug in the instrumentation
5152
Context context = request.ctx().apply(Helpers.OTEL_CONTEXT_KEY);
52-
return context != null && context != Context.root() ? context.makeCurrent() : null;
53+
return context.makeCurrent();
5354
}
5455
return null;
5556
}

instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/Helpers.java

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.twitter.finagle.http.Request;
1414
import com.twitter.finagle.http.Request$;
1515
import com.twitter.finagle.http.collection.RecordSchema;
16+
import com.twitter.finagle.http2.transport.common.Http2StreamMessageHandler;
1617
import io.netty.channel.Channel;
1718
import io.netty.channel.ChannelHandler;
1819
import io.netty.channel.ChannelHandlerContext;
@@ -26,16 +27,18 @@
2627
import io.opentelemetry.context.Context;
2728
import io.opentelemetry.instrumentation.api.util.VirtualField;
2829
import io.opentelemetry.instrumentation.netty.v4_1.internal.AttributeKeys;
29-
import io.opentelemetry.instrumentation.netty.v4_1.internal.ServerContext;
3030
import io.opentelemetry.instrumentation.netty.v4_1.internal.ServerContexts;
3131
import io.opentelemetry.instrumentation.netty.v4_1.internal.client.HttpClientTracingHandler;
3232
import io.opentelemetry.instrumentation.netty.v4_1.internal.server.HttpServerTracingHandler;
33+
import io.opentelemetry.javaagent.instrumentation.netty.common.v4_0.VirtualFieldHelper;
3334
import io.opentelemetry.javaagent.instrumentation.netty.v4_1.NettyServerSingletons;
3435

3536
public class Helpers {
3637

37-
private static final VirtualField<ChannelHandler, ChannelHandler> CHANNEL_HANDLER =
38-
VirtualField.find(ChannelHandler.class, ChannelHandler.class);
38+
private static final VirtualField<FullHttpRequest, Context> FULL_HTTP_REQUEST_CONTEXT = VirtualField.find(
39+
FullHttpRequest.class, Context.class);
40+
private static final VirtualField<HttpRequest, Context> HTTP_REQUEST_CONTEXT = VirtualField.find(
41+
HttpRequest.class, Context.class);
3942

4043
public static final RecordSchema.Field<Context> OTEL_CONTEXT_KEY =
4144
Request$.MODULE$.Schema().newField();
@@ -76,7 +79,7 @@ protected void initChannel(C channel) throws Exception {
7679
.pipeline()
7780
.addAfter(codecCtx.name(), ourHandler.getClass().getName(), ourHandler);
7881
// attach this in this way to match up with how netty instrumentation expects things
79-
CHANNEL_HANDLER.set(codecCtx.handler(), ourHandler);
82+
VirtualFieldHelper.CHANNEL_HANDLER.set(codecCtx.handler(), ourHandler);
8083
}
8184
}
8285
}
@@ -116,7 +119,7 @@ protected void initChannel(C channel) throws Exception {
116119
.pipeline()
117120
.addAfter(codecCtx.name(), ourHandler.getClass().getName(), ourHandler);
118121
// attach this in this way to match up with how netty instrumentation expects things
119-
CHANNEL_HANDLER.set(codecCtx.handler(), ourHandler);
122+
VirtualFieldHelper.CHANNEL_HANDLER.set(codecCtx.handler(), ourHandler);
120123
}
121124
}
122125
}
@@ -127,9 +130,15 @@ protected void initChannel(C channel) throws Exception {
127130
Part 1/3 of bridging the otel Context from netty to finagle (for h2).
128131
*/
129132
public static void mutateHandlerPipeline(Channel ch) {
130-
ChannelHandler codec = ch.pipeline().get(Netty4HttpPackageHelpers.getHttpCodecName());
131-
132-
if (codec instanceof HttpServerCodec) {
133+
ChannelHandler h1Handler = ch.pipeline().get(Netty4HttpPackageHelpers.getHttpCodecName());
134+
Http2StreamMessageHandler h2Handler = ch.pipeline().get(Http2StreamMessageHandler.class);
135+
136+
// h1 server handler || h2 server handler;
137+
// private class on a semi-private type -- not bothering to extract that any other way
138+
if (
139+
h1Handler instanceof HttpServerCodec || (h2Handler != null && h2Handler.getClass().getName()
140+
.equals(
141+
"com.twitter.finagle.http2.transport.common.Http2StreamMessageHandler$ServerHttp2StreamMessageHandler"))) {
133142
// ensure we capture the server context and assign it to the outgoing request before offering
134143
// to the AsyncQueue;
135144
// not applicable to clients
@@ -145,26 +154,12 @@ public static void mutateHandlerPipeline(Channel ch) {
145154
*/
146155
@Override
147156
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
148-
ServerContexts serverContexts = ServerContexts.get(ctx.channel());
149-
if (serverContexts == null) {
150-
super.channelRead(ctx, msg);
151-
return;
152-
}
153-
154-
ServerContext serverContext = serverContexts.peekLast();
155-
156157
// type switch courtesy of
157158
// com.twitter.finagle.netty4.http.Netty4ServerStreamTransport.read()
158159
if (msg instanceof FullHttpRequest) {
159-
VirtualField.find(FullHttpRequest.class, Context.class)
160-
.set(
161-
(FullHttpRequest) msg,
162-
serverContext != null ? serverContext.context() : null);
160+
FULL_HTTP_REQUEST_CONTEXT.set((FullHttpRequest) msg, Context.current());
163161
} else if (msg instanceof HttpRequest) {
164-
VirtualField.find(HttpRequest.class, Context.class)
165-
.set(
166-
(HttpRequest) msg,
167-
serverContext != null ? serverContext.context() : null);
162+
HTTP_REQUEST_CONTEXT.set((HttpRequest) msg, Context.current());
168163
} else {
169164
throw new IllegalArgumentException("unexpected request type: " + msg);
170165
}
@@ -182,9 +177,9 @@ public static void chainContextToFinagle(Object msg, Request request) {
182177
Context context;
183178
// type switch courtesy of com.twitter.finagle.netty4.http.Netty4ServerStreamTransport.read()
184179
if (msg instanceof FullHttpRequest) {
185-
context = VirtualField.find(FullHttpRequest.class, Context.class).get((FullHttpRequest) msg);
180+
context = FULL_HTTP_REQUEST_CONTEXT.get((FullHttpRequest) msg);
186181
} else if (msg instanceof HttpRequest) {
187-
context = VirtualField.find(HttpRequest.class, Context.class).get((HttpRequest) msg);
182+
context = HTTP_REQUEST_CONTEXT.get((HttpRequest) msg);
188183
} else {
189184
// shouldn't practically reach here
190185
throw new IllegalArgumentException("unexpected request type: " + msg);

instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/LocalSchedulerActivationInstrumentation.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,8 @@ public static Runnable wrap(@Advice.Argument(0) Runnable task) {
4646
return null;
4747
}
4848

49+
// always set it: you never know what might be polluting the thread local context at the time
4950
Context context = Java8BytecodeBridge.currentContext();
50-
if (context == Context.root()) {
51-
return task;
52-
}
5351
return ContextPropagatingRunnable.propagateContext(task, context);
5452
}
5553
}

instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/PromiseInterruptibleInstrumentation.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ public static PartialFunction<Throwable, BoxedUnit> onEnter(
4545
return handler;
4646
}
4747
Context context = Context.current();
48-
if (context == Context.root()) {
49-
return handler;
50-
}
5148
return new TwitterUtilCoreHelpers.InterruptibleWithContext(context, handler);
5249
}
5350
}

instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/PromiseKInstrumentation.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,22 @@ public static class TrapContextAdvice {
4848
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
4949
public static void onExit(@Advice.This Promise.K thiz) {
5050
Context current = Context.current();
51-
if (current != Context.root()) {
52-
TwitterUtilCoreHelpers.PROMISE_K_CONTEXT_FIELD.set(thiz, current);
53-
}
51+
TwitterUtilCoreHelpers.PROMISE_K_CONTEXT_FIELD.set(thiz, current);
5452
}
5553
}
5654

5755
@SuppressWarnings("unused")
5856
public static class ApplyAdvice {
5957
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
6058
public static Scope onApplyEnter(@Advice.This Promise.K thiz) {
59+
// if this is null, there's a bug in the instrumentation
6160
Context savedContext = TwitterUtilCoreHelpers.PROMISE_K_CONTEXT_FIELD.get(thiz);
62-
if (savedContext != null) {
63-
return savedContext.makeCurrent();
64-
}
65-
return null;
61+
return savedContext.makeCurrent();
6662
}
6763

6864
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
6965
public static void onApplyExit(@Advice.Enter Scope scope) {
70-
if (scope != null) {
71-
scope.close();
72-
}
66+
scope.close();
7367
}
7468
}
7569
}

instrumentation/finagle-http-23.11/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/TwitterUtilCoreHelpers.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,17 @@ public BoxedUnit apply(Throwable x) {
4949
}
5050

5151
public static <T, O> Function1<T, O> wrap(Context context, Function1<T, O> fn) {
52-
if (context == Context.root()) {
53-
return fn;
54-
}
5552
return (t) -> {
53+
// always set it: you never know what might be polluting the thread local context at the time
5654
try (Scope ignored = context.makeCurrent()) {
5755
return fn.apply(t);
5856
}
5957
};
6058
}
6159

6260
public static <T> Function0<T> wrap(Context context, Function0<T> fn) {
63-
if (context == Context.root()) {
64-
return fn;
65-
}
6661
return () -> {
62+
// always set it: you never know what might be polluting the thread local context at the time
6763
try (Scope ignored = context.makeCurrent()) {
6864
return fn.apply();
6965
}

instrumentation/finagle-http-23.11/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/finaglehttp/v23_11/ServerH2Test.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.twitter.finagle.http2.param.PriorKnowledge;
1919
import com.twitter.util.Await;
2020
import com.twitter.util.Duration;
21+
import io.opentelemetry.api.trace.SpanKind;
2122
import io.opentelemetry.instrumentation.netty.v4_1.internal.ProtocolSpecificEvent;
2223
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
2324
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
@@ -55,6 +56,9 @@ private static void assertSwitchingProtocolsEvent(EventDataAssert eventDataAsser
5556
equalTo(ProtocolSpecificEvent.SWITCHING_PROTOCOLS_TO_KEY, singletonList("h2c")));
5657
}
5758

59+
/*
60+
Bonus is that this implicitly tests both the server & client h2 upgrades.
61+
*/
5862
@Test
5963
void h2ProtocolUpgrade() throws Exception {
6064
URI uri = URI.create("http://localhost:" + port + SUCCESS.getPath());
@@ -66,7 +70,7 @@ void h2ProtocolUpgrade() throws Exception {
6670
cleanup.deferCleanup(client::close);
6771

6872
Response response =
69-
Await.result(
73+
testing.runWithSpan("h2-upgrade-client", () -> Await.result(
7074
client.apply(
7175
Utils.buildRequest(
7276
"GET",
@@ -76,7 +80,7 @@ void h2ProtocolUpgrade() throws Exception {
7680
TEST_USER_AGENT,
7781
HttpHeaderNames.X_FORWARDED_FOR.toString(),
7882
TEST_CLIENT_IP))),
79-
Duration.fromSeconds(20));
83+
Duration.fromSeconds(20)));
8084

8185
assertThat(response.status().code()).isEqualTo(SUCCESS.getStatus());
8286
assertThat(response.contentString()).isEqualTo(SUCCESS.getBody());
@@ -87,19 +91,23 @@ void h2ProtocolUpgrade() throws Exception {
8791
testing.waitAndAssertTraces(
8892
trace -> {
8993
List<Consumer<SpanDataAssert>> spanAssertions = new ArrayList<>();
94+
// client initiation
9095
spanAssertions.add(
91-
s -> s.hasEventsSatisfyingExactly(ServerH2Test::assertSwitchingProtocolsEvent));
96+
s -> s.hasName("h2-upgrade-client").hasNoParent().hasKind(SpanKind.INTERNAL));
97+
// actual client netty span (including upgrade event)
9298
spanAssertions.add(
93-
span -> {
94-
assertServerSpan(span, method, endpoint, endpoint.getStatus());
95-
span.hasEventsSatisfyingExactly(ServerH2Test::assertSwitchingProtocolsEvent);
96-
});
97-
98-
int parentIndex = 1;
99+
s -> s.hasKind(SpanKind.CLIENT).hasName(method).hasParent(trace.getSpan(0))
100+
.hasEventsSatisfyingExactly(ServerH2Test::assertSwitchingProtocolsEvent));
101+
// server netty span (including upgrade event)
102+
spanAssertions.add(
103+
span -> assertServerSpan(span, method, endpoint, endpoint.getStatus())
104+
.hasParent(trace.getSpan(1))
105+
.hasEventsSatisfyingExactly(ServerH2Test::assertSwitchingProtocolsEvent));
106+
// server controller span
99107
spanAssertions.add(
100108
span -> {
101109
assertControllerSpan(span, null);
102-
span.hasParent(trace.getSpan(parentIndex));
110+
span.hasParent(trace.getSpan(2));
103111
});
104112

105113
trace.hasSpansSatisfyingExactly(spanAssertions);

0 commit comments

Comments
 (0)