Fixes null bugs on WebClient canceled request (#1548)

This commit is contained in:
Adrian Cole
2020-02-06 15:02:12 +08:00
committed by GitHub
parent c2cd689ec7
commit 3c0932bb32
2 changed files with 42 additions and 17 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.web.client;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -131,9 +132,14 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
}
};
private static final String CLIENT_SPAN_KEY = "sleuth.webclient.clientSpan";
static final String CLIENT_SPAN_KEY = "sleuth.webclient.clientSpan";
private static final String CANCELLED_SUBSCRIPTION_ERROR = "CANCELLED";
static final Exception CANCELLED_ERROR = new CancellationException("CANCELLED") {
@Override
public Throwable fillInStackTrace() {
return this; // stack trace doesn't add value here
}
};
final ConfigurableApplicationContext springContext;
@@ -170,6 +176,8 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
}
MonoWebClientTrace trace = new MonoWebClientTrace(next, wrapper.buildRequest(),
this, span);
// TODO: investigate why this commit leaks a scope:
// 8f5bcdabd7af23df443e771432eb85597f3b3076
tracer().withSpanInScope(parentSpan);
return trace;
}
@@ -356,13 +364,14 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
return this.context;
}
void handleReceive(Span clientSpan, ClientResponse clientResponse,
Throwable throwable) {
void handleReceive(Span clientSpan, @Nullable ClientResponse res,
@Nullable Throwable error) {
if (log.isTraceEnabled()) {
log.trace("Handling receive");
}
this.handler.handleReceive(new HttpClientResponse(clientResponse),
throwable, clientSpan);
HttpClientResponse response = res != null ? new HttpClientResponse(res)
: null;
this.handler.handleReceive(response, error, clientSpan);
if (log.isTraceEnabled()) {
log.trace("Closed scope");
}
@@ -374,32 +383,31 @@ final class TraceExchangeFilterFunction implements ExchangeFilterFunction {
+ this.span + "]");
}
this.span.tag("error", CANCELLED_SUBSCRIPTION_ERROR);
handleReceive(this.span, null, null);
handleReceive(this.span, null, CANCELLED_ERROR);
}
void terminateSpan(@Nullable ClientResponse clientResponse,
@Nullable Throwable throwable) {
@Nullable Throwable error) {
if (clientResponse == null) {
if (log.isDebugEnabled()) {
log.debug("No response was returned. Will close the span ["
+ this.span + "]");
}
handleReceive(this.span, clientResponse, throwable);
handleReceive(this.span, null, error);
return;
}
int statusCode = clientResponse.rawStatusCode();
boolean error = statusCode >= 400;
if (error) {
boolean isHttpError = statusCode >= 400;
if (isHttpError) {
if (log.isDebugEnabled()) {
log.debug(
"Non positive status code was returned from the call. Will close the span ["
+ this.span + "]");
}
throwable = new RestClientException(
error = new RestClientException(
"Status code of the response is [" + statusCode + "]");
}
handleReceive(this.span, clientResponse, throwable);
handleReceive(this.span, clientResponse, error);
}
}

View File

@@ -40,6 +40,7 @@ import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
@@ -55,6 +56,8 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.reactivestreams.Subscription;
import reactor.core.publisher.BaseSubscriber;
import zipkin2.Annotation;
import zipkin2.reporter.Reporter;
@@ -112,8 +115,7 @@ public class WebClientTests {
static final String SAMPLED_NAME = "X-B3-Sampled";
static final String PARENT_ID_NAME = "X-B3-ParentSpanId";
private static final org.apache.commons.logging.Log log = LogFactory
.getLog(WebClientTests.class);
private static final Log log = LogFactory.getLog(WebClientTests.class);
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@@ -351,7 +353,7 @@ public class WebClientTests {
@Test
@SuppressWarnings("unchecked")
public void shouldWorkWhenCustomStatusCodeIsReturned() throws InterruptedException {
public void shouldWorkWhenCustomStatusCodeIsReturned() {
Span span = this.tracer.nextSpan().name("foo").start();
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
@@ -370,6 +372,21 @@ public class WebClientTests {
.contains("CLIENT");
}
@Test
public void shouldTagOnCancel() {
this.webClient.get().uri("http://localhost:" + this.port + "/doNotSkip")
.retrieve().bodyToMono(String.class)
.subscribe(new BaseSubscriber<String>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
cancel();
}
});
then(this.reporter.getSpans()).isNotEmpty();
then(this.reporter.getSpans().get(0).tags()).containsEntry("error", "CANCELLED");
}
@Test
public void shouldRespectSkipPattern() {
this.webClient.get().uri("http://localhost:" + this.port + "/skip").retrieve()