Skip to content

Commit 0ca6b28

Browse files
committed
Fix NPE in netty client where a future got cancelled before the execution id was attached to the channel
1 parent e0328b7 commit 0ca6b28

File tree

3 files changed

+40
-5
lines changed

3 files changed

+40
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Netty NIO Async HTTP Client",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Fixed an issue in Netty async http client where NPE was thrown when the execution got cancelled before executionId was attached to the channel."
6+
}

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.netty.channel.ChannelHandler;
2222
import io.netty.channel.ChannelHandlerContext;
2323
import io.netty.channel.ChannelInboundHandlerAdapter;
24+
import io.netty.util.Attribute;
2425
import java.io.IOException;
2526
import software.amazon.awssdk.annotations.SdkInternalApi;
2627
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyClientLogger;
@@ -46,7 +47,17 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
4647

4748
FutureCancelledException cancelledException = (FutureCancelledException) e;
4849

49-
if (currentRequestCancelled(ctx, cancelledException)) {
50+
Long channelExecutionId = executionId(ctx);
51+
52+
if (channelExecutionId == null) {
53+
RequestContext requestContext = ctx.channel().attr(REQUEST_CONTEXT_KEY).get();
54+
LOG.debug(ctx.channel(), () -> String.format("Received a cancellation exception on a channel that doesn't have an "
55+
+ "execution Id attached yet. Exception's execution ID is %d. "
56+
+ "Exception is being ignored. Closing the channel",
57+
executionId(ctx)));
58+
ctx.close();
59+
requestContext.channelPool().release(ctx.channel());
60+
} else if (currentRequestCancelled(channelExecutionId, cancelledException)) {
5061
RequestContext requestContext = ctx.channel().attr(REQUEST_CONTEXT_KEY).get();
5162
requestContext.handler().onError(e);
5263
ctx.fireExceptionCaught(new IOException("Request cancelled"));
@@ -65,11 +76,16 @@ public static FutureCancelHandler getInstance() {
6576
return INSTANCE;
6677
}
6778

68-
private boolean currentRequestCancelled(ChannelHandlerContext ctx, FutureCancelledException e) {
69-
return e.getExecutionId() == executionId(ctx);
79+
private static boolean currentRequestCancelled(long executionId, FutureCancelledException e) {
80+
return e.getExecutionId() == executionId;
7081
}
7182

72-
private Long executionId(ChannelHandlerContext ctx) {
73-
return ctx.channel().attr(EXECUTION_ID_KEY).get();
83+
private static Long executionId(ChannelHandlerContext ctx) {
84+
Attribute<Long> attr = ctx.channel().attr(EXECUTION_ID_KEY);
85+
if (attr == null) {
86+
return null;
87+
}
88+
89+
return attr.get();
7490
}
7591
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package software.amazon.awssdk.http.nio.netty.internal;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.Matchers.any;
20+
import static org.mockito.Mockito.never;
1921
import static org.mockito.Mockito.verify;
2022
import static org.mockito.Mockito.when;
2123
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.EXECUTION_ID_KEY;
@@ -98,4 +100,15 @@ public void forwardsExceptionIfNotCancelledException() {
98100
verify(ctx).fireExceptionCaught(exceptionCaptor.capture());
99101
assertThat(exceptionCaptor.getValue()).isEqualTo(err);
100102
}
103+
104+
@Test
105+
public void cancelledException_executionIdNull_shouldIgnoreExceptionAndCloseChannel() {
106+
when(channel.attr(EXECUTION_ID_KEY)).thenReturn(null);
107+
108+
FutureCancelledException cancelledException = new FutureCancelledException(1L, new CancellationException());
109+
handler.exceptionCaught(ctx, cancelledException);
110+
111+
verify(ctx, never()).fireExceptionCaught(any(Throwable.class));
112+
verify(ctx).close();
113+
}
101114
}

0 commit comments

Comments
 (0)