Skip to content

Commit aa31ccc

Browse files
authored
Fix NPE in netty client (#3031)
* Fix NPE in netty client where a future got cancelled before the execution id was attached to the channel * Not throwing FutureCancelledException if it doesn't have executionId.
1 parent 3abbf0d commit aa31ccc

File tree

4 files changed

+46
-7
lines changed

4 files changed

+46
-7
lines changed
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

+21-5
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.warn(ctx.channel(), () -> String.format("Received a cancellation exception on a channel that doesn't have an "
55+
+ "execution Id attached. 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/main/java/software/amazon/awssdk/http/nio/netty/internal/NettyRequestExecutor.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import io.netty.handler.codec.http.HttpVersion;
4343
import io.netty.handler.timeout.ReadTimeoutHandler;
4444
import io.netty.handler.timeout.WriteTimeoutHandler;
45+
import io.netty.util.Attribute;
4546
import io.netty.util.concurrent.Future;
4647
import io.netty.util.concurrent.GenericFutureListener;
4748
import io.netty.util.concurrent.Promise;
@@ -130,8 +131,9 @@ private CompletableFuture<Void> createExecutionFuture(Promise<Channel> channelPr
130131
Channel ch = channelPromise.getNow();
131132
try {
132133
ch.eventLoop().submit(() -> {
133-
if (ch.attr(IN_USE).get()) {
134-
ch.pipeline().fireExceptionCaught(new FutureCancelledException(executionId, t));
134+
Attribute<Long> executionIdKey = ch.attr(EXECUTION_ID_KEY);
135+
if (ch.attr(IN_USE) != null && ch.attr(IN_USE).get() && executionIdKey != null) {
136+
ch.pipeline().fireExceptionCaught(new FutureCancelledException(this.executionId, t));
135137
} else {
136138
ch.close().addListener(closeFuture -> context.channelPool().release(ch));
137139
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
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;
2224
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.REQUEST_CONTEXT_KEY;
2325

2426
import io.netty.channel.Channel;
2527
import io.netty.channel.ChannelHandlerContext;
28+
import io.netty.channel.DefaultChannelId;
2629
import io.netty.channel.EventLoopGroup;
2730
import io.netty.util.DefaultAttributeMap;
2831
import java.io.IOException;
@@ -75,6 +78,7 @@ public void methodSetup() {
7578
when(ctx.channel()).thenReturn(channel);
7679
when(channel.attr(EXECUTION_ID_KEY)).thenReturn(attrMap.attr(EXECUTION_ID_KEY));
7780
when(channel.attr(REQUEST_CONTEXT_KEY)).thenReturn(attrMap.attr(REQUEST_CONTEXT_KEY));
81+
when(channel.id()).thenReturn(DefaultChannelId.newInstance());
7882
}
7983

8084
@Test
@@ -98,4 +102,15 @@ public void forwardsExceptionIfNotCancelledException() {
98102
verify(ctx).fireExceptionCaught(exceptionCaptor.capture());
99103
assertThat(exceptionCaptor.getValue()).isEqualTo(err);
100104
}
105+
106+
@Test
107+
public void cancelledException_executionIdNull_shouldIgnoreExceptionAndCloseChannel() {
108+
when(channel.attr(EXECUTION_ID_KEY)).thenReturn(null);
109+
110+
FutureCancelledException cancelledException = new FutureCancelledException(1L, new CancellationException());
111+
handler.exceptionCaught(ctx, cancelledException);
112+
113+
verify(ctx, never()).fireExceptionCaught(any(Throwable.class));
114+
verify(ctx).close();
115+
}
101116
}

0 commit comments

Comments
 (0)