Skip to content

Commit

Permalink
Fix a bug in Spring Cloud Gateway if HttpClientFinalizer#send does no…
Browse files Browse the repository at this point in the history
…t invoke, the span created at NettyRoutingFilterInterceptor can not stop. (#672)
  • Loading branch information
cylx3126 authored Mar 7, 2024
1 parent 4adb343 commit f227543
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Release Notes.
* Rename system env name from `sw_plugin_kafka_producer_config` to `SW_PLUGIN_KAFKA_PRODUCER_CONFIG`.
* Support for ActiveMQ-Artemis messaging tracing.
* Archive the expired plugins `impala-jdbc-2.6.x-plugin`.
* Fix a bug in Spring Cloud Gateway if HttpClientFinalizer#send does not invoke, the span created at NettyRoutingFilterInterceptor can not stop.

#### Documentation
* Update docs to describe `expired-plugins`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public static EnhancedInstance getInstance(Object o) {
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
if (ContextManager.isActive()) {
// if HttpClientFinalizerSendInterceptor does not invoke, we will stop the span there to avoid context leak.
ContextManager.stopSpan();
}
return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,23 @@
import java.security.Principal;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.apache.skywalking.apm.agent.core.context.ContextManager;
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
Expand All @@ -47,6 +57,103 @@

@RunWith(TracingSegmentRunner.class)
public class NettyRoutingFilterInterceptorTest {

private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance {
private ContextSnapshot snapshot;
Map<String, Object> attributes = new HashMap<>();

@Override
public Object getSkyWalkingDynamicField() {
return snapshot;
}

@Override
public void setSkyWalkingDynamicField(Object value) {
this.snapshot = (ContextSnapshot) value;
}

@Override
public ServerHttpRequest getRequest() {
return null;
}

@Override
public ServerHttpResponse getResponse() {
return null;
}

@Override
public Map<String, Object> getAttributes() {
return attributes;
}

@Override
public Mono<WebSession> getSession() {
return null;
}

@Override
public <T extends Principal> Mono<T> getPrincipal() {
return null;
}

@Override
public Mono<MultiValueMap<String, String>> getFormData() {
return null;
}

@Override
public Mono<MultiValueMap<String, Part>> getMultipartData() {
return null;
}

@Override
public LocaleContext getLocaleContext() {
return null;
}

@Override
public ApplicationContext getApplicationContext() {
return null;
}

@Override
public boolean isNotModified() {
return false;
}

@Override
public boolean checkNotModified(Instant instant) {
return false;
}

@Override
public boolean checkNotModified(String s) {
return false;
}

@Override
public boolean checkNotModified(String s, Instant instant) {
return false;
}

@Override
public String transformUrl(String s) {
return null;
}

@Override
public void addUrlTransformer(Function<String, String> function) {

}

@Override
public String getLogPrefix() {
return null;
}
}

private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance();
private final NettyRoutingFilterInterceptor interceptor = new NettyRoutingFilterInterceptor();
@Rule
public AgentServiceRule serviceRule = new AgentServiceRule();
Expand Down Expand Up @@ -151,12 +258,48 @@ public void testIsTraced() throws Throwable {
interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null);
interceptor.afterMethod(null, null, null, null, null);
Assert.assertEquals(exchange.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true);
Assert.assertNotNull(ContextManager.activeSpan());
Assert.assertFalse(ContextManager.isActive());

ContextManager.stopSpan();
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();

interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null);
interceptor.afterMethod(null, null, null, null, null);
Assert.assertEquals(exchange.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true);
}

@Test
public void testWithNullDynamicField() throws Throwable {
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
Assert.assertEquals(traceSegments.size(), 1);
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
Assert.assertNotNull(spans);
Assert.assertEquals(spans.size(), 1);
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
}

@Test
public void testWithContextSnapshot() throws Throwable {
final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null);
SpanLayer.asHttp(entrySpan);
entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX);
enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();
ContextManager.stopSpan(entrySpan);
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
Assert.assertEquals(traceSegments.size(), 1);
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
Assert.assertNotNull(spans);
Assert.assertEquals(spans.size(), 2);
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX);
SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ private EnhancedInstance getInstance(Object o) {
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
if (ContextManager.isActive()) {
// if HttpClientFinalizerSendInterceptor does not invoke, we will stop the span there to avoid context leak.
ContextManager.stopSpan();
}
return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ public void setUp() throws Exception {
public void testWithNullDynamicField() throws Throwable {
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
ContextManager.stopSpan();
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
Assert.assertEquals(traceSegments.size(), 1);
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
Expand All @@ -187,7 +188,8 @@ public void testWithContextSnapshot() throws Throwable {
enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
ContextManager.stopSpan();
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();
ContextManager.stopSpan(entrySpan);
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
Assert.assertEquals(traceSegments.size(), 1);
Expand All @@ -207,9 +209,10 @@ public void testIsTraced() throws Throwable {
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
Assert.assertEquals(enhancedInstance.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true);
Assert.assertNotNull(ContextManager.activeSpan());
Assert.assertFalse(ContextManager.isActive());

ContextManager.stopSpan();
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();

interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ private EnhancedInstance getInstance(Object o) {
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
if (ContextManager.isActive()) {
// if HttpClientFinalizerSendInterceptor does not invoke, we will stop the span there to avoid context leak.
ContextManager.stopSpan();
}
return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ public void setUp() throws Exception {
public void testWithNullDynamicField() throws Throwable {
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
ContextManager.stopSpan();
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
Assert.assertEquals(traceSegments.size(), 1);
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
Expand All @@ -187,7 +188,8 @@ public void testWithContextSnapshot() throws Throwable {
enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
ContextManager.stopSpan();
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();
ContextManager.stopSpan(entrySpan);
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
Assert.assertEquals(traceSegments.size(), 1);
Expand All @@ -207,9 +209,10 @@ public void testIsTraced() throws Throwable {
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
Assert.assertEquals(enhancedInstance.getAttributes().get(NETTY_ROUTING_FILTER_TRACED_ATTR), true);
Assert.assertNotNull(ContextManager.activeSpan());
Assert.assertFalse(ContextManager.isActive());

ContextManager.stopSpan();
// no more need this, span was stopped at interceptor#afterMethod
// ContextManager.stopSpan();

interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
interceptor.afterMethod(null, null, null, null, null);
Expand Down

0 comments on commit f227543

Please sign in to comment.