Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand the set of retryable exceptions in JdkHttpSender #5942

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Map;
Expand All @@ -32,6 +31,7 @@
import java.util.zip.GZIPOutputStream;
import javax.annotation.Nullable;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;

/**
* {@link HttpSender} which is backed by JDK {@link HttpClient}.
Expand Down Expand Up @@ -221,7 +221,13 @@ private HttpResponse<byte[]> sendRequest(
}

private static boolean isRetryableException(IOException throwable) {
return throwable instanceof HttpTimeoutException;
// Almost all IOExceptions we've encountered are transient retryable, so we opt out of specific
// IOExceptions that are unlikely to resolve rather than opting in.
// Known retryable IOException messages: "Connection reset", "/{remote ip}:{remote port} GOAWAY
// received"
// Known retryable HttpTimeoutException messages: "request timed out"
// Known retryable HttpConnectTimeoutException messages: "HTTP connect timed out"
return !(throwable instanceof SSLException);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should

static boolean isRetryableException(IOException e) {
if (!(e instanceof SocketTimeoutException)) {
return false;
}
String message = e.getMessage();
// Connect timeouts can produce SocketTimeoutExceptions with no message, or with "connect timed
// out"
return message == null || message.toLowerCase(Locale.ROOT).contains("connect timed out");
}

be also modified?

Copy link
Member Author

@jack-berg jack-berg Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the behavior of the JDK HttpClient is applicable to OkHttp client. I think in principle we should retry in all the same scenarios for both the JDK HttpClient and OkHttp, but not sure how each failure mode manifests as exceptions in OkHttp.

}

private static class NoCopyByteArrayOutputStream extends ByteArrayOutputStream {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.net.http.HttpConnectTimeoutException;
import java.time.Duration;
import java.util.Collections;
import javax.net.ssl.SSLException;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -69,9 +70,20 @@ void sendInternal_RetryableConnectTimeoutException() throws IOException, Interru
verify(mockHttpClient, times(2)).send(any(), any());
}

@Test
void sendInternal_RetryableIoException() throws IOException, InterruptedException {
doThrow(new IOException("error!")).when(mockHttpClient).send(any(), any());

assertThatThrownBy(() -> sender.sendInternal(marshaler -> {}))
.isInstanceOf(IOException.class)
.hasMessage("error!");

verify(mockHttpClient, times(2)).send(any(), any());
}

@Test
void sendInternal_NonRetryableException() throws IOException, InterruptedException {
doThrow(new IOException("unknown error")).when(mockHttpClient).send(any(), any());
doThrow(new SSLException("unknown error")).when(mockHttpClient).send(any(), any());

assertThatThrownBy(() -> sender.sendInternal(marshaler -> {}))
.isInstanceOf(IOException.class)
Expand Down