Skip to content

Commit 7ab5e78

Browse files
mccullsdevflow.devflow-routing-intake
andauthored
Remove noopScope() usage from vertx-redis-client advice (#12008)
Remove noopScope() usage from vertx-redis-client advice Increment/decrement the RedisAPI call-depth guard as the first statement of enter/exit advice instead of returning AgentTracer.noopScope() mid-method. This fixes a call-depth leak on the "parent span already REDIS_COMMAND" branch. Also drops RedisAPICallAdvice's vestigial always-true boolean return and @Advice.Local in favor of returning the AgentScope directly via @Advice.Enter, guarding the now-possible null scope on close/cleanup. Since the depth increment now happens before the Request-handled dedup check, mark the Request as handled unconditionally on the nested path too, instead of skipping it — otherwise a later async re-send of the same Request (e.g. via a pooled connection) could be mistaken for a brand-new command and get double-spanned. Guard the activeSpan() fallback used for connection decoration so it only tags a span that is actually a REDIS_COMMAND span, rather than whatever happens to be active — this avoids mistagging an unrelated span with Redis peer/connection info when the fallback resolves to something other than the intended command span. Adds a regression test asserting the call-depth counter is balanced after each test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Merge remote-tracking branch 'origin/master' into mcculls/cleanup-redis-advice Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 2e78b36 commit 7ab5e78

4 files changed

Lines changed: 43 additions & 31 deletions

File tree

dd-java-agent/instrumentation/vertx/vertx-redis-client/vertx-redis-client-3.9/src/main/java/datadog/trace/instrumentation/vertx_redis_client/RedisAPICallAdvice.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@
2222

2323
public class RedisAPICallAdvice {
2424
@Advice.OnMethodEnter(suppress = Throwable.class)
25-
public static boolean beforeCall(
25+
public static AgentScope beforeCall(
2626
@Advice.Origin final Method currentMethod,
2727
@Advice.This final RedisAPI self,
28-
@Advice.Local("callScope") AgentScope scope,
2928
@Advice.Argument(
3029
value = 0,
3130
readOnly = false,
@@ -62,7 +61,7 @@ public static boolean beforeCall(
6261
// either), so this seems to be the only way to communicate that we have already wrapped
6362
// the handler. :(
6463
if (CallDepthThreadLocalMap.incrementCallDepth(RedisAPI.class) > 0) {
65-
return true;
64+
return null;
6665
}
6766

6867
// TODO what is the recreated for every read about in the @Advice.Origin javadoc?
@@ -100,7 +99,7 @@ public static boolean beforeCall(
10099
}
101100

102101
if (null == handler || handler instanceof ResponseHandlerWrapper) {
103-
return true;
102+
return null;
104103
}
105104

106105
final AgentSpan parentSpan = activeSpan();
@@ -112,7 +111,7 @@ public static boolean beforeCall(
112111
The potential racy condition when the handler may be added to an already finished task is handled
113112
by RedisAPIImplSendAdvice.
114113
*/
115-
scope = activateSpan(clientSpan);
114+
AgentScope scope = activateSpan(clientSpan);
116115
ResponseHandlerWrapper respHandler =
117116
new ResponseHandlerWrapper(handler, clientSpan, parentContinuation);
118117
handler = respHandler;
@@ -139,23 +138,24 @@ public static boolean beforeCall(
139138
Store the response handler in the context so that it can be retrieved in RedisAPIImplSendAdvice
140139
*/
141140
InstrumentationContext.get(RedisAPI.class, ResponseHandlerWrapper.class).put(self, respHandler);
142-
return true;
141+
return scope;
143142
}
144143

145144
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
146145
public static void afterCall(
147146
@Advice.Thrown final Throwable throwable,
148147
@Advice.This final RedisAPI self,
149-
@Advice.Local("callScope") AgentScope scope,
150-
@Advice.Enter final boolean decrement) {
151-
if (decrement) {
152-
CallDepthThreadLocalMap.decrementCallDepth(RedisAPI.class);
148+
@Advice.Enter final AgentScope scope) {
149+
CallDepthThreadLocalMap.decrementCallDepth(RedisAPI.class);
150+
151+
if (null == scope) {
152+
return;
153153
}
154154

155155
scope.close();
156156

157157
// Clean the response handler from the context
158-
InstrumentationContext.get(RedisAPI.class, ResponseHandlerWrapper.class).put(self, null);
158+
InstrumentationContext.get(RedisAPI.class, ResponseHandlerWrapper.class).remove(self);
159159
}
160160

161161
// Only apply this advice for versions that we instrument 3.9.x

dd-java-agent/instrumentation/vertx/vertx-redis-client/vertx-redis-client-3.9/src/main/java/datadog/trace/instrumentation/vertx_redis_client/RedisFutureSendAdvice.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
44
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
55
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.captureSpan;
6-
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopScope;
76
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan;
87
import static datadog.trace.instrumentation.vertx_redis_client.VertxRedisClientDecorator.DECORATE;
98
import static datadog.trace.instrumentation.vertx_redis_client.VertxRedisClientDecorator.REDIS_COMMAND;
@@ -32,6 +31,10 @@ public static AgentScope beforeSend(
3231
@Advice.Argument(value = 0, readOnly = false) Request request,
3332
@Advice.Local("ddParentContinuation") ContextContinuation parentContinuation)
3433
throws Throwable {
34+
// If we had already wrapped the innermost handler in the RedisAPI call, then we should
35+
// not wrap it again here. See comment in RedisAPICallAdvice
36+
boolean nested = CallDepthThreadLocalMap.incrementCallDepth(RedisAPI.class) > 0;
37+
3538
ContextStore<Request, Boolean> ctxt = InstrumentationContext.get(Request.class, Boolean.class);
3639
Boolean handled = ctxt.get(request);
3740
if (null != handled && handled) {
@@ -44,6 +47,12 @@ public static AgentScope beforeSend(
4447
}
4548
ctxt.put(request, Boolean.TRUE);
4649

50+
// Mark the request handled even when nested, so a later async re-send of the same
51+
// Request (e.g. via a pooled connection) isn't mistaken for a brand-new command.
52+
if (nested) {
53+
return null;
54+
}
55+
4756
AgentSpan parentSpan = activeSpan();
4857

4958
if (parentSpan != null && REDIS_COMMAND.equals(parentSpan.getOperationName())) {
@@ -53,12 +62,6 @@ public static AgentScope beforeSend(
5362

5463
parentContinuation = null == parentSpan ? captureSpan(noopSpan()) : captureSpan(parentSpan);
5564

56-
// If we had already wrapped the innermost handler in the RedisAPI call, then we should
57-
// not wrap it again here. See comment in RedisAPICallAdvice
58-
if (CallDepthThreadLocalMap.incrementCallDepth(RedisAPI.class) > 0) {
59-
return noopScope();
60-
}
61-
6265
final AgentSpan clientSpan =
6366
DECORATE.startAndDecorateSpan(
6467
request.command(), InstrumentationContext.get(Command.class, UTF8BytesString.class));
@@ -72,20 +75,19 @@ public static void afterSend(
7275
@Advice.Local("ddParentContinuation") ContextContinuation parentContinuation,
7376
@Advice.Enter final AgentScope clientScope,
7477
@Advice.This final Object thiz) {
78+
CallDepthThreadLocalMap.decrementCallDepth(RedisAPI.class);
7579
if (thiz instanceof RedisConnection) {
7680
final SocketAddress socketAddress =
7781
InstrumentationContext.get(RedisConnection.class, SocketAddress.class)
7882
.get((RedisConnection) thiz);
7983
final AgentSpan span = clientScope != null ? clientScope.span() : activeSpan();
80-
81-
if (socketAddress != null && span != null) {
82-
final AgentSpan spanWithConnection = clientScope == noopScope() ? activeSpan() : span;
83-
DECORATE.onConnection(spanWithConnection, socketAddress);
84-
DECORATE.setPeerPort(spanWithConnection, socketAddress.port());
84+
// Verify the activeSpan() fallback is actually a REDIS_COMMAND span
85+
if (socketAddress != null && span != null && REDIS_COMMAND.equals(span.getOperationName())) {
86+
DECORATE.onConnection(span, socketAddress);
87+
DECORATE.setPeerPort(span, socketAddress.port());
8588
}
8689
}
8790
if (clientScope != null) {
88-
CallDepthThreadLocalMap.decrementCallDepth(RedisAPI.class);
8991
Promise<Response> promise = Promise.promise();
9092
responseFuture.onComplete(
9193
new ResponseHandler(promise, clientScope.span(), parentContinuation));

dd-java-agent/instrumentation/vertx/vertx-redis-client/vertx-redis-client-3.9/src/main/java/datadog/trace/instrumentation/vertx_redis_client/RedisSendAdvice.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
44
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activeSpan;
55
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.captureSpan;
6-
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopScope;
76
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan;
87
import static datadog.trace.instrumentation.vertx_redis_client.VertxRedisClientDecorator.DECORATE;
98
import static datadog.trace.instrumentation.vertx_redis_client.VertxRedisClientDecorator.REDIS_COMMAND;
@@ -33,6 +32,10 @@ public static AgentScope beforeSend(
3332
@Advice.Argument(value = 0, readOnly = false) Request request,
3433
@Advice.Argument(value = 1, readOnly = false) Handler<AsyncResult<Response>> handler)
3534
throws Throwable {
35+
// If we had already wrapped the innermost handler in the RedisAPI call, then we should
36+
// not wrap it again here. See comment in RedisAPICallAdvice
37+
boolean nested = CallDepthThreadLocalMap.incrementCallDepth(RedisAPI.class) > 0;
38+
3639
if (null == handler || handler instanceof ResponseHandlerWrapper) {
3740
return null;
3841
}
@@ -49,10 +52,10 @@ public static AgentScope beforeSend(
4952
}
5053
ctxt.put(request, Boolean.TRUE);
5154

52-
// If we had already wrapped the innermost handler in the RedisAPI call, then we should
53-
// not wrap it again here. See comment in RedisAPICallAdvice
54-
if (CallDepthThreadLocalMap.incrementCallDepth(RedisAPI.class) > 0) {
55-
return noopScope();
55+
// Mark the request handled even when nested, so a later async re-send of the same
56+
// Request (e.g. via a pooled connection) isn't mistaken for a brand-new command.
57+
if (nested) {
58+
return null;
5659
}
5760

5861
AgentSpan parentSpan = activeSpan();
@@ -74,18 +77,19 @@ public static AgentScope beforeSend(
7477
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
7578
public static void afterSend(
7679
@Advice.Enter final AgentScope clientScope, @Advice.This final Object thiz) {
80+
CallDepthThreadLocalMap.decrementCallDepth(RedisAPI.class);
7781
if (thiz instanceof RedisConnection) {
7882
final SocketAddress socketAddress =
7983
InstrumentationContext.get(RedisConnection.class, SocketAddress.class)
8084
.get((RedisConnection) thiz);
8185
final AgentSpan span = clientScope != null ? clientScope.span() : activeSpan();
82-
if (socketAddress != null && span != null) {
86+
// Verify the activeSpan() fallback is actually a REDIS_COMMAND span
87+
if (socketAddress != null && span != null && REDIS_COMMAND.equals(span.getOperationName())) {
8388
DECORATE.onConnection(span, socketAddress);
8489
DECORATE.setPeerPort(span, socketAddress.port());
8590
}
8691
}
8792
if (null != clientScope) {
88-
CallDepthThreadLocalMap.decrementCallDepth(RedisAPI.class);
8993
clientScope.close();
9094
}
9195
}

dd-java-agent/instrumentation/vertx/vertx-redis-client/vertx-redis-client-3.9/src/test/groovy/VertxRedisTestBase.groovy

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import datadog.trace.agent.test.asserts.ListWriterAssert
33
import datadog.trace.agent.test.asserts.TraceAssert
44
import datadog.trace.agent.test.naming.VersionedNamingTestBase
55
import datadog.trace.api.DDSpanTypes
6+
import datadog.trace.bootstrap.CallDepthThreadLocalMap
67
import datadog.trace.bootstrap.instrumentation.api.Tags
78
import datadog.trace.core.DDSpan
89
import io.vertx.core.AsyncResult
@@ -12,6 +13,7 @@ import io.vertx.core.Vertx
1213
import io.vertx.core.VertxOptions
1314
import io.vertx.redis.client.Command
1415
import io.vertx.redis.client.Redis
16+
import io.vertx.redis.client.RedisAPI
1517
import io.vertx.redis.client.Request
1618
import io.vertx.redis.client.Response
1719
import org.testcontainers.containers.wait.strategy.Wait
@@ -75,6 +77,10 @@ abstract class VertxRedisTestBase extends VersionedNamingTestBase {
7577
cleanUpTraces()
7678
}
7779

80+
def cleanup() {
81+
assert CallDepthThreadLocalMap.getCallDepth(RedisAPI) == 0
82+
}
83+
7884
void cleanUpTraces() {
7985
def cleanupSpan = runUnderTrace("cleanup") {
8086
redis.send(Request.cmd(Command.FLUSHALL), Promise.promise())

0 commit comments

Comments
 (0)