Skip to content

Commit ed086cb

Browse files
vandonramarziali
andauthored
[🍒7900] Finish netty span when request is cancelled (#7945)
* Finish netty span when request is cancelled (#7900) * Finish netty span when request is cancelled * Propagate channelInactive to next handler * Support testing of latest quartz versions (#7948) --------- Co-authored-by: Andrea Marziali <andrea.marziali@datadoghq.com>
1 parent 6b59d04 commit ed086cb

File tree

6 files changed

+100
-0
lines changed

6 files changed

+100
-0
lines changed

dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/HttpServerRequestTracingHandler.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,25 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
7171
DECORATE.onError(span, throwable);
7272
DECORATE.beforeFinish(span);
7373
span.finish(); // Finish the span manually since finishSpanOnClose was false
74+
ctx.channel().attr(SPAN_ATTRIBUTE_KEY).remove();
7475
throw throwable;
7576
}
7677
}
7778
}
79+
80+
@Override
81+
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
82+
try {
83+
super.channelInactive(ctx);
84+
} finally {
85+
try {
86+
final AgentSpan span = ctx.channel().attr(SPAN_ATTRIBUTE_KEY).getAndRemove();
87+
if (span != null && span.phasedFinish()) {
88+
// at this point we can just publish this span to avoid loosing the rest of the trace
89+
span.publish();
90+
}
91+
} catch (final Throwable ignored) {
92+
}
93+
}
94+
}
7895
}

dd-java-agent/instrumentation/netty-4.0/src/main/java/datadog/trace/instrumentation/netty40/server/HttpServerResponseTracingHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ public void write(final ChannelHandlerContext ctx, final Object msg, final Chann
3434
DECORATE.onError(span, throwable);
3535
span.setHttpStatusCode(500);
3636
span.finish(); // Finish the span manually since finishSpanOnClose was false
37+
ctx.channel().attr(SPAN_ATTRIBUTE_KEY).remove();
3738
throw throwable;
3839
}
3940
if (response.getStatus() != HttpResponseStatus.CONTINUE) {
4041
DECORATE.onResponse(span, response);
4142
DECORATE.beforeFinish(span);
43+
ctx.channel().attr(SPAN_ATTRIBUTE_KEY).remove();
4244
span.finish(); // Finish the span manually since finishSpanOnClose was false
4345
}
4446
}

dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/HttpServerRequestTracingHandler.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,25 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
7676
DECORATE.onError(span, throwable);
7777
DECORATE.beforeFinish(span);
7878
span.finish(); // Finish the span manually since finishSpanOnClose was false
79+
ctx.channel().attr(SPAN_ATTRIBUTE_KEY).remove();
7980
throw throwable;
8081
}
8182
}
8283
}
84+
85+
@Override
86+
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
87+
try {
88+
super.channelInactive(ctx);
89+
} finally {
90+
try {
91+
final AgentSpan span = ctx.channel().attr(SPAN_ATTRIBUTE_KEY).getAndRemove();
92+
if (span != null && span.phasedFinish()) {
93+
// at this point we can just publish this span to avoid loosing the rest of the trace
94+
span.publish();
95+
}
96+
} catch (final Throwable ignored) {
97+
}
98+
}
99+
}
83100
}

dd-java-agent/instrumentation/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/server/HttpServerResponseTracingHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public void write(final ChannelHandlerContext ctx, final Object msg, final Chann
3636
DECORATE.onError(span, throwable);
3737
span.setHttpStatusCode(500);
3838
span.finish(); // Finish the span manually since finishSpanOnClose was false
39+
ctx.channel().attr(SPAN_ATTRIBUTE_KEY).remove();
3940
throw throwable;
4041
}
4142
if (response.status() != HttpResponseStatus.CONTINUE
@@ -44,6 +45,7 @@ public void write(final ChannelHandlerContext ctx, final Object msg, final Chann
4445
DECORATE.onResponse(span, response);
4546
DECORATE.beforeFinish(span);
4647
span.finish(); // Finish the span manually since finishSpanOnClose was false
48+
ctx.channel().attr(SPAN_ATTRIBUTE_KEY).remove();
4749
}
4850
}
4951
}

dd-java-agent/instrumentation/spring-webflux-6/src/bootTest/groovy/SpringWebfluxTest.groovy

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import reactor.core.publisher.Mono
2222
import reactor.netty.http.HttpProtocol
2323
import reactor.netty.http.client.HttpClient
2424

25+
import java.time.Duration
26+
2527
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
2628
classes = [SpringWebFluxTestApplication],
2729
properties = "server.http2.enabled=true")
@@ -656,6 +658,59 @@ class SpringWebfluxHttp11Test extends AgentTestRunner {
656658
"annotation API delayed response" | "/foo-delayed" | "/foo-delayed" | "getFooDelayed" | new FooModel(3L, "delayed").toString()
657659
}
658660

661+
def "Cancellation should always release the server span"() {
662+
setup:
663+
String url = "http://localhost:$port/very-delayed"
664+
when:
665+
def response = client.get().uri(url).exchange().timeout(Duration.ofSeconds(2)).block()
666+
667+
then:
668+
thrown Exception
669+
assert response == null
670+
assertTraces(2) {
671+
def traceParent
672+
sortSpansByStart()
673+
trace(2) {
674+
clientSpan(it, null, "http.request", "spring-webflux-client", "GET", URI.create(url), null, false, null, false,
675+
[ "message":"The subscription was cancelled", "event":"cancelled"])
676+
traceParent = clientSpan(it, span(0), "netty.client.request", "netty-client", "GET", URI.create(url), null)
677+
}
678+
trace(2) {
679+
span {
680+
resourceName "GET /very-delayed"
681+
operationName "netty.request"
682+
spanType DDSpanTypes.HTTP_SERVER
683+
childOf(traceParent)
684+
tags {
685+
"$Tags.COMPONENT" "netty"
686+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
687+
"$Tags.PEER_HOST_IPV4" "127.0.0.1"
688+
"$Tags.PEER_PORT" Integer
689+
"$Tags.HTTP_URL" url
690+
"$Tags.HTTP_HOSTNAME" "localhost"
691+
"$Tags.HTTP_METHOD" "GET"
692+
"$Tags.HTTP_USER_AGENT" String
693+
"$Tags.HTTP_CLIENT_IP" "127.0.0.1"
694+
"$Tags.HTTP_ROUTE" "/very-delayed"
695+
defaultTags(true)
696+
}
697+
}
698+
span {
699+
resourceName "TestController.getVeryDelayedMono"
700+
operationName "TestController.getVeryDelayedMono"
701+
spanType DDSpanTypes.HTTP_SERVER
702+
childOfPrevious()
703+
tags {
704+
"$Tags.COMPONENT" "spring-webflux-controller"
705+
"$Tags.SPAN_KIND" Tags.SPAN_KIND_SERVER
706+
"handler.type" TestController.getName()
707+
defaultTags()
708+
}
709+
}
710+
}
711+
}
712+
}
713+
659714
def clientSpan(
660715
TraceAssert trace,
661716
Object parentSpan,

dd-java-agent/instrumentation/spring-webflux-6/src/bootTest/groovy/dd/trace/instrumentation/springwebflux/server/TestController.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import datadog.trace.api.Trace
44
import org.springframework.web.bind.annotation.GetMapping
55
import org.springframework.web.bind.annotation.PathVariable
66
import org.springframework.web.bind.annotation.RestController
7+
import org.springframework.web.reactive.function.server.ServerResponse
78
import reactor.core.publisher.Mono
89

910
import java.time.Duration
@@ -56,6 +57,12 @@ class TestController {
5657
return Mono.just(id).delayElement(Duration.ofMillis(100)).map { i -> tracedMethod(i) }
5758
}
5859

60+
@GetMapping("/very-delayed")
61+
Mono<ServerResponse> getVeryDelayedMono() {
62+
return Mono.delay(Duration.ofSeconds(30)) // long enough not to finish
63+
.then(ServerResponse.status(200).build())
64+
}
65+
5966
@Trace()
6067
private FooModel tracedMethod(long id) {
6168
return new FooModel(id, "tracedMethod")

0 commit comments

Comments
 (0)