diff --git a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java index fda90c6a8c7..25866a162fc 100644 --- a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java +++ b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java @@ -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; @@ -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}. @@ -221,7 +221,13 @@ private HttpResponse 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); } private static class NoCopyByteArrayOutputStream extends ByteArrayOutputStream { diff --git a/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java b/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java index 2691bb1ca3f..19cb3f997ac 100644 --- a/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java +++ b/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java @@ -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; @@ -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)