Skip to content

Commit 96e67ec

Browse files
committed
Safely declare Netty AttributeKeys
This commit prevents the Netty client from throwing an exception in cases where it tries to declare an attribute key and the key already exists. This can happen when separate instances of the SDK are loaded by different classloaders, but the Netty classes loaded by a third and shared by the other classloaders. Fixes #1886
1 parent d155ac8 commit 96e67ec

File tree

8 files changed

+79
-22
lines changed

8 files changed

+79
-22
lines changed

build-tools/src/main/resources/software/amazon/awssdk/checkstyle.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,14 @@
365365
<property name="ignoreComments" value="true"/>
366366
</module>
367367

368+
<!-- Checks that we don't use AttributeKey.newInstance directly -->
369+
<module name="Regexp">
370+
<property name="format" value="AttributeKey\.newInstance"/>
371+
<property name="illegalPattern" value="true"/>
372+
<property name="message" value="Use NettyUtils.getOrCreateAttributeKey to safely declare AttributeKeys"/>
373+
<property name="ignoreComments" value="true"/>
374+
</module>
375+
368376
<!-- Checks that we don't use plural enum names -->
369377
<module name="software.amazon.awssdk.buildtools.checkstyle.PluralEnumNames"/>
370378

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

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import software.amazon.awssdk.http.Protocol;
2727
import software.amazon.awssdk.http.nio.netty.internal.http2.Http2MultiplexedChannelPool;
2828
import software.amazon.awssdk.http.nio.netty.internal.http2.PingTracker;
29+
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils;
2930

3031
/**
3132
* Keys for attributes attached via {@link io.netty.channel.Channel#attr(AttributeKey)}.
@@ -36,70 +37,71 @@ public final class ChannelAttributeKey {
3637
/**
3738
* Future that when a protocol (http/1.1 or h2) has been selected.
3839
*/
39-
public static final AttributeKey<CompletableFuture<Protocol>> PROTOCOL_FUTURE = AttributeKey.newInstance(
40+
public static final AttributeKey<CompletableFuture<Protocol>> PROTOCOL_FUTURE = NettyUtils.getOrCreateAttributeKey(
4041
"aws.http.nio.netty.async.protocolFuture");
4142

4243
/**
4344
* Reference to {@link Http2MultiplexedChannelPool} which stores information about leased streams for a multiplexed
4445
* connection.
4546
*/
46-
public static final AttributeKey<Http2MultiplexedChannelPool> HTTP2_MULTIPLEXED_CHANNEL_POOL = AttributeKey.newInstance(
47-
"aws.http.nio.netty.async.http2MultiplexedChannelPool");
47+
public static final AttributeKey<Http2MultiplexedChannelPool> HTTP2_MULTIPLEXED_CHANNEL_POOL =
48+
NettyUtils.getOrCreateAttributeKey("aws.http.nio.netty.async.http2MultiplexedChannelPool");
4849

4950
public static final AttributeKey<PingTracker> PING_TRACKER =
50-
AttributeKey.newInstance("aws.http.nio.netty.async.h2.pingTracker");
51+
NettyUtils.getOrCreateAttributeKey("aws.http.nio.netty.async.h2.pingTracker");
5152

5253
public static final AttributeKey<Http2Connection> HTTP2_CONNECTION =
53-
AttributeKey.newInstance("aws.http.nio.netty.async.http2Connection");
54+
NettyUtils.getOrCreateAttributeKey("aws.http.nio.netty.async.http2Connection");
5455

5556
public static final AttributeKey<Integer> HTTP2_INITIAL_WINDOW_SIZE =
56-
AttributeKey.newInstance("aws.http.nio.netty.async.http2InitialWindowSize");
57+
NettyUtils.getOrCreateAttributeKey("aws.http.nio.netty.async.http2InitialWindowSize");
5758

5859
/**
5960
* Value of the MAX_CONCURRENT_STREAMS from the server's SETTING frame.
6061
*/
61-
public static final AttributeKey<Long> MAX_CONCURRENT_STREAMS = AttributeKey.newInstance(
62+
public static final AttributeKey<Long> MAX_CONCURRENT_STREAMS = NettyUtils.getOrCreateAttributeKey(
6263
"aws.http.nio.netty.async.maxConcurrentStreams");
6364

6465
/**
6566
* {@link AttributeKey} to keep track of whether we should close the connection after this request
6667
* has completed.
6768
*/
68-
static final AttributeKey<Boolean> KEEP_ALIVE = AttributeKey.newInstance("aws.http.nio.netty.async.keepAlive");
69+
static final AttributeKey<Boolean> KEEP_ALIVE = NettyUtils.getOrCreateAttributeKey("aws.http.nio.netty.async.keepAlive");
6970

7071
/**
7172
* Attribute key for {@link RequestContext}.
7273
*/
73-
static final AttributeKey<RequestContext> REQUEST_CONTEXT_KEY = AttributeKey.newInstance(
74+
static final AttributeKey<RequestContext> REQUEST_CONTEXT_KEY = NettyUtils.getOrCreateAttributeKey(
7475
"aws.http.nio.netty.async.requestContext");
7576

76-
static final AttributeKey<Subscriber<? super ByteBuffer>> SUBSCRIBER_KEY = AttributeKey.newInstance(
77+
static final AttributeKey<Subscriber<? super ByteBuffer>> SUBSCRIBER_KEY = NettyUtils.getOrCreateAttributeKey(
7778
"aws.http.nio.netty.async.subscriber");
7879

79-
static final AttributeKey<Boolean> RESPONSE_COMPLETE_KEY = AttributeKey.newInstance(
80+
static final AttributeKey<Boolean> RESPONSE_COMPLETE_KEY = NettyUtils.getOrCreateAttributeKey(
8081
"aws.http.nio.netty.async.responseComplete");
8182

8283
/**
8384
* {@link AttributeKey} to keep track of whether we have received the {@link LastHttpContent}.
8485
*/
85-
static final AttributeKey<Boolean> LAST_HTTP_CONTENT_RECEIVED_KEY = AttributeKey.newInstance(
86+
static final AttributeKey<Boolean> LAST_HTTP_CONTENT_RECEIVED_KEY = NettyUtils.getOrCreateAttributeKey(
8687
"aws.http.nio.netty.async.lastHttpContentReceived");
8788

88-
static final AttributeKey<CompletableFuture<Void>> EXECUTE_FUTURE_KEY = AttributeKey.newInstance(
89+
static final AttributeKey<CompletableFuture<Void>> EXECUTE_FUTURE_KEY = NettyUtils.getOrCreateAttributeKey(
8990
"aws.http.nio.netty.async.executeFuture");
9091

91-
static final AttributeKey<Long> EXECUTION_ID_KEY = AttributeKey.newInstance(
92+
static final AttributeKey<Long> EXECUTION_ID_KEY = NettyUtils.getOrCreateAttributeKey(
9293
"aws.http.nio.netty.async.executionId");
9394

9495
/**
9596
* Whether the channel is still in use
9697
*/
97-
static final AttributeKey<Boolean> IN_USE = AttributeKey.newInstance("aws.http.nio.netty.async.inUse");
98+
static final AttributeKey<Boolean> IN_USE = NettyUtils.getOrCreateAttributeKey("aws.http.nio.netty.async.inUse");
9899

99100
/**
100101
* Whether the channel should be closed once it is released.
101102
*/
102-
static final AttributeKey<Boolean> CLOSE_ON_RELEASE = AttributeKey.newInstance("aws.http.nio.netty.async.closeOnRelease");
103+
static final AttributeKey<Boolean> CLOSE_ON_RELEASE = NettyUtils.getOrCreateAttributeKey(
104+
"aws.http.nio.netty.async.closeOnRelease");
103105

104106
private ChannelAttributeKey() {
105107
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.net.URI;
3030
import software.amazon.awssdk.annotations.SdkInternalApi;
3131
import software.amazon.awssdk.annotations.SdkTestInternalApi;
32+
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils;
3233
import software.amazon.awssdk.utils.Logger;
3334
import software.amazon.awssdk.utils.StringUtils;
3435

@@ -37,7 +38,7 @@
3738
*/
3839
@SdkInternalApi
3940
public class Http1TunnelConnectionPool implements ChannelPool {
40-
static final AttributeKey<Boolean> TUNNEL_ESTABLISHED_KEY = AttributeKey.newInstance(
41+
static final AttributeKey<Boolean> TUNNEL_ESTABLISHED_KEY = NettyUtils.getOrCreateAttributeKey(
4142
"aws.http.nio.netty.async.Http1TunnelConnectionPool.tunnelEstablished");
4243

4344
private static final Logger log = Logger.loggerFor(Http1TunnelConnectionPool.class);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.concurrent.atomic.AtomicBoolean;
2727
import software.amazon.awssdk.annotations.SdkInternalApi;
2828
import software.amazon.awssdk.http.nio.netty.internal.http2.Http2MultiplexedChannelPool;
29+
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils;
2930

3031
/**
3132
* Wrapper around a {@link ChannelPool} to protect it from having the same channel released twice. This can
@@ -35,7 +36,8 @@
3536
@SdkInternalApi
3637
public class ReleaseOnceChannelPool implements ChannelPool {
3738

38-
private static final AttributeKey<AtomicBoolean> IS_RELEASED = AttributeKey.newInstance("isReleased");
39+
private static final AttributeKey<AtomicBoolean> IS_RELEASED = NettyUtils.getOrCreateAttributeKey(
40+
"software.amazon.awssdk.http.nio.netty.internal.http2.ReleaseOnceChannelPool.isReleased");
3941

4042
private final ChannelPool delegate;
4143

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import software.amazon.awssdk.http.Protocol;
5151
import software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey;
5252
import software.amazon.awssdk.http.nio.netty.internal.utils.BetterFixedChannelPool;
53+
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyUtils;
5354
import software.amazon.awssdk.utils.Logger;
5455
import software.amazon.awssdk.utils.Validate;
5556

@@ -72,13 +73,13 @@ public class Http2MultiplexedChannelPool implements ChannelPool {
7273
/**
7374
* Reference to the {@link MultiplexedChannelRecord} on a channel.
7475
*/
75-
private static final AttributeKey<MultiplexedChannelRecord> MULTIPLEXED_CHANNEL = AttributeKey.newInstance(
76-
"software.amazon.awssdk.http.nio.netty.internal.http2.Http2MultiplexedChannelPool.MULTIPLEXED_CHANNEL");
76+
private static final AttributeKey<MultiplexedChannelRecord> MULTIPLEXED_CHANNEL = NettyUtils.getOrCreateAttributeKey(
77+
"software.amazon.awssdk.http.nio.netty.internal.http2.Http2MultiplexedChannelPool.MULTIPLEXED_CHANNEL");
7778

7879
/**
7980
* Whether a parent channel has been released yet. This guards against double-releasing to the delegate connection pool.
8081
*/
81-
private static final AttributeKey<Boolean> RELEASED = AttributeKey.newInstance(
82+
private static final AttributeKey<Boolean> RELEASED = NettyUtils.getOrCreateAttributeKey(
8283
"software.amazon.awssdk.http.nio.netty.internal.http2.Http2MultiplexedChannelPool.RELEASED");
8384

8485
private final ChannelPool connectionPool;

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

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

1818
import io.netty.channel.EventLoop;
19+
import io.netty.util.AttributeKey;
1920
import io.netty.util.concurrent.EventExecutor;
2021
import io.netty.util.concurrent.Future;
2122
import io.netty.util.concurrent.GenericFutureListener;
@@ -160,4 +161,16 @@ public static void warnIfNotInEventLoop(EventLoop loop) {
160161
log.warn(() -> "Execution is happening outside of the expected event loop.", exception);
161162
}
162163
}
164+
165+
/**
166+
* @return an {@code AttributeKey} for {@code attr}. This returns an existing instance if it was previously created.
167+
*/
168+
public static <T> AttributeKey<T> getOrCreateAttributeKey(String attr) {
169+
if (AttributeKey.exists(attr)) {
170+
return AttributeKey.valueOf(attr);
171+
}
172+
//CHECKSTYLE:OFF - This is the only place allowed to call AttributeKey.newInstance()
173+
return AttributeKey.newInstance(attr);
174+
//CHECKSTYLE:ON
175+
}
163176
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
@SdkInternalApi
3232
public class OrderedWriteChannelHandlerContext extends DelegatingChannelHandlerContext {
3333
private static final AttributeKey<Void> ORDERED =
34-
AttributeKey.newInstance("aws.http.nio.netty.async.OrderedWriteChannelHandlerContext.ORDERED");
34+
NettyUtils.getOrCreateAttributeKey("aws.http.nio.netty.async.OrderedWriteChannelHandlerContext.ORDERED");
3535

3636
private OrderedWriteChannelHandlerContext(ChannelHandlerContext delegate) {
3737
super(delegate);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.nio.netty.internal.utils;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import io.netty.util.AttributeKey;
21+
import org.junit.Test;
22+
23+
public class NettyUtilsTest {
24+
@Test
25+
public void testGetOrCreateAttributeKey_calledTwiceWithSameName_returnsSameInstance() {
26+
String attr = "NettyUtilsTest.Foo";
27+
AttributeKey<String> fooAttr = NettyUtils.getOrCreateAttributeKey(attr);
28+
assertThat(NettyUtils.getOrCreateAttributeKey(attr)).isSameAs(fooAttr);
29+
}
30+
}

0 commit comments

Comments
 (0)