Skip to content

Commit e011839

Browse files
authored
fix(netty-nio-client): fix streaming timeout exception decoration so read timeouts are retried (#7048)
1 parent 85d102d commit e011839

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Netty NIO HTTP Client",
4+
"description": "Decorate streaming response publisher failures so S3AsyncClient getObject can retry Netty read timeouts.",
5+
"contributor": "goutamadwant"
6+
}

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,11 +342,12 @@ public void onError(Throwable t) {
342342
if (!isDone.compareAndSet(false, true)) {
343343
return;
344344
}
345+
Throwable throwable = NettyUtils.decorateException(channelContext.channel(), t);
345346
try {
346347
runAndLogError(channelContext.channel(),
347348
() -> String.format("Subscriber %s threw an exception in onError.", subscriber),
348-
() -> subscriber.onError(t));
349-
notifyError(t);
349+
() -> subscriber.onError(throwable));
350+
notifyError(throwable);
350351
} finally {
351352
runAndLogError(channelContext.channel(), () -> "Could not release channel back to the pool",
352353
() -> closeAndRelease(channelContext));

http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/PublisherAdapterTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@
3737
import io.netty.handler.codec.http.HttpContent;
3838
import io.netty.handler.codec.http.HttpResponseStatus;
3939
import io.netty.handler.codec.http.HttpVersion;
40+
import io.netty.handler.timeout.ReadTimeoutException;
4041
import io.reactivex.Flowable;
42+
import java.io.IOException;
4143
import java.net.URI;
4244
import java.nio.ByteBuffer;
4345
import java.util.concurrent.CompletableFuture;
4446
import org.junit.Before;
4547
import org.junit.Test;
4648
import org.junit.runner.RunWith;
49+
import org.mockito.ArgumentCaptor;
4750
import org.mockito.Mock;
4851
import org.mockito.junit.MockitoJUnitRunner;
4952
import org.reactivestreams.Publisher;
@@ -167,6 +170,33 @@ public void errorOccurred_shouldInvokeResponseHandler() {
167170
verify(responseHandler).onError(exception);
168171
}
169172

173+
@Test
174+
public void onError_whenReadTimeoutException_decoratesToRetryableIOException() {
175+
Flowable<HttpContent> testPublisher = Flowable.error(ReadTimeoutException.INSTANCE);
176+
177+
StreamedHttpResponse streamedHttpResponse = new DefaultStreamedHttpResponse(HttpVersion.HTTP_1_1,
178+
HttpResponseStatus.ACCEPTED,
179+
testPublisher);
180+
181+
182+
183+
ResponseHandler.PublisherAdapter publisherAdapter = new ResponseHandler.PublisherAdapter(streamedHttpResponse,
184+
ctx,
185+
requestContext,
186+
executeFuture
187+
);
188+
TestSubscriber subscriber = new TestSubscriber();
189+
190+
publisherAdapter.subscribe(subscriber);
191+
192+
ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
193+
verify(responseHandler).onError(errorCaptor.capture());
194+
assertThat(errorCaptor.getValue()).isInstanceOf(IOException.class)
195+
.hasCauseInstanceOf(ReadTimeoutException.class);
196+
assertThat(subscriber.error).isSameAs(errorCaptor.getValue());
197+
assertThat(executeFuture).isCompletedExceptionally();
198+
}
199+
170200
@Test
171201
public void subscriptionCancelled_upstreamPublisherCallsOnNext_httpContentReleased() {
172202
HttpContent firstContent = mock(HttpContent.class);
@@ -290,6 +320,7 @@ static final class TestSubscriber implements Subscriber<ByteBuffer> {
290320
private Subscription subscription;
291321
private boolean isCompleted = false;
292322
private boolean errorOccurred = false;
323+
private Throwable error;
293324

294325
@Override
295326
public void onSubscribe(Subscription s) {
@@ -305,6 +336,7 @@ public void onNext(ByteBuffer byteBuffer) {
305336
@Override
306337
public void onError(Throwable t) {
307338
errorOccurred = true;
339+
error = t;
308340
}
309341

310342
@Override

0 commit comments

Comments
 (0)