Skip to content

Commit 8cd6f5b

Browse files
nikpzoewangg
andauthored
Add the ability to configure connection timeout, keep-alive settings, and advanced SocketOptions on the AwsCrtAsyncHttpClient. This is necessary for any clients with long-running connections that exceed default socket timeouts of services along the call path, and need to enable keep-alive settings which the CRT client supports, but the Java client wasn't exposing to callers (#3457)
Co-authored-by: Zoe Wang <[email protected]>
1 parent 8e6958a commit 8cd6f5b

File tree

6 files changed

+307
-8
lines changed

6 files changed

+307
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "nikp",
5+
"description": "Add the ability to configure connection timeout, and enabling keep-alive with interval/timeout settings on the AwsCrtAsyncHttpClient to support long-running connections"
6+
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@
218218
<Method name="execute"/>
219219
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
220220
</Match>
221+
<Match>
222+
<Class name="software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient"/>
223+
<Method name="&lt;init&gt;"/>
224+
<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
225+
</Match>
226+
221227

222228
<!-- For forward-compatibility with this having members (i.e. the checksum type) -->
223229
<Match>

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java

+97-6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import software.amazon.awssdk.utils.AttributeMap;
4747
import software.amazon.awssdk.utils.IoUtils;
4848
import software.amazon.awssdk.utils.Logger;
49+
import software.amazon.awssdk.utils.NumericUtils;
4950
import software.amazon.awssdk.utils.Validate;
5051

5152
/**
@@ -64,6 +65,14 @@ public final class AwsCrtAsyncHttpClient implements SdkAsyncHttpClient {
6465
private static final String AWS_COMMON_RUNTIME = "AwsCommonRuntime";
6566
private static final int DEFAULT_STREAM_WINDOW_SIZE = 16 * 1024 * 1024; // 16 MB
6667

68+
private static final Duration CRT_SDK_DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(3);
69+
// Override default connection timeout for Crt client to be in line with the CRT default:
70+
// https://github.com/awslabs/aws-crt-java/blob/main/src/main/java/software/amazon/awssdk/crt/io/SocketOptions.java#L79
71+
private static final AttributeMap CRT_HTTP_DEFAULTS =
72+
AttributeMap.builder()
73+
.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, CRT_SDK_DEFAULT_CONNECTION_TIMEOUT)
74+
.build();
75+
6776
private final Map<URI, HttpClientConnectionManager> connectionPools = new ConcurrentHashMap<>();
6877
private final LinkedList<CrtResource> ownedSubResources = new LinkedList<>();
6978
private final ClientBootstrap bootstrap;
@@ -84,7 +93,7 @@ private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) {
8493
Validate.isPositive(builder.readBufferSize, "readBufferSize");
8594

8695
try (ClientBootstrap clientBootstrap = new ClientBootstrap(null, null);
87-
SocketOptions clientSocketOptions = new SocketOptions();
96+
SocketOptions clientSocketOptions = buildSocketOptions(builder, config);
8897
TlsContextOptions clientTlsContextOptions = TlsContextOptions.createDefaultClient() // NOSONAR
8998
.withCipherPreference(builder.cipherPreference)
9099
.withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES));
@@ -138,6 +147,26 @@ private HttpProxyOptions buildProxyOptions(ProxyConfiguration proxyConfiguration
138147
return clientProxyOptions;
139148
}
140149

150+
private SocketOptions buildSocketOptions(DefaultBuilder builder, AttributeMap config) {
151+
SocketOptions clientSocketOptions = new SocketOptions();
152+
153+
Duration connectionTimeout = config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT);
154+
if (connectionTimeout != null) {
155+
clientSocketOptions.connectTimeoutMs = NumericUtils.saturatedCast(connectionTimeout.toMillis());
156+
}
157+
158+
TcpKeepAliveConfiguration tcpKeepAliveConfiguration = builder.tcpKeepAliveConfiguration;
159+
if (tcpKeepAliveConfiguration != null) {
160+
clientSocketOptions.keepAliveIntervalSecs =
161+
NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveInterval().getSeconds());
162+
clientSocketOptions.keepAliveTimeoutSecs =
163+
NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveTimeout().getSeconds());
164+
165+
}
166+
167+
return clientSocketOptions;
168+
}
169+
141170
/**
142171
* Marks a Native CrtResource as owned by the current Java Object.
143172
*
@@ -312,10 +341,10 @@ public interface Builder extends SdkAsyncHttpClient.Builder<AwsCrtAsyncHttpClien
312341
Builder proxyConfiguration(Consumer<ProxyConfiguration.Builder> proxyConfigurationBuilderConsumer);
313342

314343
/**
315-
* Configure the health checks for for all connections established by this client.
344+
* Configure the health checks for all connections established by this client.
316345
*
317346
* <p>
318-
* eg: you can set a throughput threshold for the a connection to be considered healthy.
347+
* eg: you can set a throughput threshold for a connection to be considered healthy.
319348
* If the connection falls below this threshold for a configurable amount of time,
320349
* then the connection is considered unhealthy and will be shut down.
321350
*
@@ -325,10 +354,10 @@ public interface Builder extends SdkAsyncHttpClient.Builder<AwsCrtAsyncHttpClien
325354
Builder connectionHealthChecksConfiguration(ConnectionHealthChecksConfiguration healthChecksConfiguration);
326355

327356
/**
328-
* A convenience method to configure the health checks for for all connections established by this client.
357+
* A convenience method to configure the health checks for all connections established by this client.
329358
*
330359
* <p>
331-
* eg: you can set a throughput threshold for the a connection to be considered healthy.
360+
* eg: you can set a throughput threshold for a connection to be considered healthy.
332361
* If the connection falls below this threshold for a configurable amount of time,
333362
* then the connection is considered unhealthy and will be shut down.
334363
*
@@ -340,9 +369,46 @@ Builder connectionHealthChecksConfiguration(Consumer<ConnectionHealthChecksConfi
340369
healthChecksConfigurationBuilder);
341370

342371
/**
343-
* Configure the maximum amount of time that a connection should be allowed to remain open while idle.
372+
* The amount of time to wait when initially establishing a connection before giving up and timing out. The maximum
373+
* possible value, in ms, is the value of {@link Integer#MAX_VALUE}, any longer duration will be reduced to the maximum
374+
* possible value. If not specified, the connection timeout duration will be set to value defined in
375+
* {@link AwsCrtAsyncHttpClient#CRT_SDK_DEFAULT_CONNECTION_TIMEOUT}.
344376
*/
345377
Builder connectionMaxIdleTime(Duration connectionMaxIdleTime);
378+
379+
/**
380+
* Configure connection socket timeout
381+
*/
382+
Builder connectionTimeout(Duration connectionTimeout);
383+
384+
/**
385+
* Configure whether to enable TCP Keep-alive and relevant configuration for all connections established by this client.
386+
*
387+
* <p>
388+
* By default, keepAlive is disabled and this is not required.
389+
* tcpKeepAlive is enabled by providing this configuration and specifying
390+
* periodic keepalive packet intervals and timeouts
391+
* This may be required for certain connections for longer durations than default socket timeouts
392+
*
393+
* @param tcpKeepAliveConfiguration The TCP keep-alive configuration to use
394+
* @return The builder of the method chaining.
395+
*/
396+
Builder tcpKeepAliveConfiguration(TcpKeepAliveConfiguration tcpKeepAliveConfiguration);
397+
398+
/**
399+
* Configure whether to enable TCP Keep-alive and relevant configuration for all connections established by this client.
400+
*
401+
* <p>
402+
* By default, keepAlive is disabled and this is not required.
403+
* tcpKeepAlive is enabled by providing this configuration and specifying
404+
* periodic keepalive packet intervals and timeouts
405+
* This may be required for certain connections for longer durations than default socket timeouts
406+
*
407+
* @param tcpKeepAliveConfigurationBuilder The TCP keep-alive configuration builder to use
408+
* @return The builder of the method chaining.
409+
*/
410+
Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveConfiguration.Builder>
411+
tcpKeepAliveConfigurationBuilder);
346412
}
347413

348414
/**
@@ -355,20 +421,23 @@ private static final class DefaultBuilder implements Builder {
355421
private int readBufferSize = DEFAULT_STREAM_WINDOW_SIZE;
356422
private ProxyConfiguration proxyConfiguration;
357423
private ConnectionHealthChecksConfiguration connectionHealthChecksConfiguration;
424+
private TcpKeepAliveConfiguration tcpKeepAliveConfiguration;
358425

359426
private DefaultBuilder() {
360427
}
361428

362429
@Override
363430
public SdkAsyncHttpClient build() {
364431
return new AwsCrtAsyncHttpClient(this, standardOptions.build()
432+
.merge(CRT_HTTP_DEFAULTS)
365433
.merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS));
366434
}
367435

368436
@Override
369437
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
370438
return new AwsCrtAsyncHttpClient(this, standardOptions.build()
371439
.merge(serviceDefaults)
440+
.merge(CRT_HTTP_DEFAULTS)
372441
.merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS));
373442
}
374443

@@ -417,10 +486,32 @@ public Builder connectionHealthChecksConfiguration(Consumer<ConnectionHealthChec
417486

418487
@Override
419488
public Builder connectionMaxIdleTime(Duration connectionMaxIdleTime) {
489+
Validate.isPositive(connectionMaxIdleTime, "connectionMaxIdleTime");
420490
standardOptions.put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, connectionMaxIdleTime);
421491
return this;
422492
}
423493

494+
@Override
495+
public Builder connectionTimeout(Duration connectionTimeout) {
496+
Validate.isPositive(connectionTimeout, "connectionTimeout");
497+
standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, connectionTimeout);
498+
return this;
499+
}
500+
501+
@Override
502+
public Builder tcpKeepAliveConfiguration(TcpKeepAliveConfiguration tcpKeepAliveConfiguration) {
503+
this.tcpKeepAliveConfiguration = tcpKeepAliveConfiguration;
504+
return this;
505+
}
506+
507+
@Override
508+
public Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveConfiguration.Builder>
509+
tcpKeepAliveConfigurationBuilder) {
510+
TcpKeepAliveConfiguration.Builder builder = TcpKeepAliveConfiguration.builder();
511+
tcpKeepAliveConfigurationBuilder.accept(builder);
512+
return tcpKeepAliveConfiguration(builder.build());
513+
}
514+
424515
@Override
425516
public Builder proxyConfiguration(Consumer<ProxyConfiguration.Builder> proxyConfigurationBuilderConsumer) {
426517
ProxyConfiguration.Builder builder = ProxyConfiguration.builder();

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/ConnectionHealthChecksConfiguration.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import software.amazon.awssdk.utils.Validate;
2222

2323
/**
24-
* Configuration that defines health checks for for all connections established by
25-
* the{@link ConnectionHealthChecksConfiguration}.
24+
* Configuration that defines health checks for all connections established by
25+
* the {@link ConnectionHealthChecksConfiguration}.
2626
*
2727
* <b>NOTE:</b> This is a Preview API and is subject to change so it should not be used in production.
2828
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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.crt;
17+
18+
import java.time.Duration;
19+
import software.amazon.awssdk.annotations.SdkPreviewApi;
20+
import software.amazon.awssdk.annotations.SdkPublicApi;
21+
import software.amazon.awssdk.utils.Validate;
22+
23+
/**
24+
* Configuration that defines keep-alive options for all connections established by
25+
* the {@link TcpKeepAliveConfiguration}.
26+
*
27+
* <b>NOTE:</b> This is a Preview API and is subject to change so it should not be used in production.
28+
*/
29+
@SdkPublicApi
30+
@SdkPreviewApi
31+
public final class TcpKeepAliveConfiguration {
32+
33+
private final Duration keepAliveInterval;
34+
private final Duration keepAliveTimeout;
35+
36+
private TcpKeepAliveConfiguration(DefaultTcpKeepAliveConfigurationBuilder builder) {
37+
this.keepAliveInterval = Validate.isPositive(builder.keepAliveInterval,
38+
"keepAliveInterval");
39+
this.keepAliveTimeout = Validate.isPositive(builder.keepAliveTimeout,
40+
"keepAliveTimeout");
41+
}
42+
43+
/**
44+
* @return number of seconds between TCP keepalive packets being sent to the peer
45+
*/
46+
public Duration keepAliveInterval() {
47+
return keepAliveInterval;
48+
}
49+
50+
/**
51+
* @return number of seconds to wait for a keepalive response before considering the connection timed out
52+
*/
53+
public Duration keepAliveTimeout() {
54+
return keepAliveTimeout;
55+
}
56+
57+
public static Builder builder() {
58+
return new DefaultTcpKeepAliveConfigurationBuilder();
59+
}
60+
61+
/**
62+
* A builder for {@link TcpKeepAliveConfiguration}.
63+
*
64+
* <p>All implementations of this interface are mutable and not thread safe.</p>
65+
*/
66+
public interface Builder {
67+
/**
68+
* Sets the Duration between TCP keepalive packets being sent to the peer
69+
* @param keepAliveInterval Duration between TCP keepalive packets being sent to the peer
70+
* @return Builder
71+
*/
72+
Builder keepAliveInterval(Duration keepAliveInterval);
73+
74+
/**
75+
* Sets the Duration to wait for a keepalive response before considering the connection timed out
76+
* @param keepAliveTimeout Duration to wait for a keepalive response before considering the connection timed out
77+
* @return Builder
78+
*/
79+
Builder keepAliveTimeout(Duration keepAliveTimeout);
80+
81+
TcpKeepAliveConfiguration build();
82+
}
83+
84+
/**
85+
* An SDK-internal implementation of {@link Builder}.
86+
*/
87+
private static final class DefaultTcpKeepAliveConfigurationBuilder implements Builder {
88+
private Duration keepAliveInterval;
89+
private Duration keepAliveTimeout;
90+
91+
private DefaultTcpKeepAliveConfigurationBuilder() {
92+
}
93+
94+
/**
95+
* Sets the Duration between TCP keepalive packets being sent to the peer
96+
* @param keepAliveInterval Duration between TCP keepalive packets being sent to the peer
97+
* @return Builder
98+
*/
99+
@Override
100+
public Builder keepAliveInterval(Duration keepAliveInterval) {
101+
this.keepAliveInterval = keepAliveInterval;
102+
return this;
103+
}
104+
105+
/**
106+
* Sets the Duration to wait for a keepalive response before considering the connection timed out
107+
* @param keepAliveTimeout Duration to wait for a keepalive response before considering the connection timed out
108+
* @return Builder
109+
*/
110+
@Override
111+
public Builder keepAliveTimeout(Duration keepAliveTimeout) {
112+
this.keepAliveTimeout = keepAliveTimeout;
113+
return this;
114+
}
115+
116+
@Override
117+
public TcpKeepAliveConfiguration build() {
118+
return new TcpKeepAliveConfiguration(this);
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)