Skip to content

Commit c7a1786

Browse files
authored
Add client configuration overriding of SCHEDULED_EXECUTOR_SERVICE option (#4002)
* Add client configuration overriding of SCHEDULED_EXECUTOR_SERVICE option * review * review
1 parent 637de29 commit c7a1786

File tree

6 files changed

+246
-5
lines changed

6 files changed

+246
-5
lines changed
Lines changed: 6 additions & 0 deletions
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": "scrocquesel",
5+
"description": "Add client configuration overriding of SCHEDULED_EXECUTOR_SERVICE option"
6+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
9292
import software.amazon.awssdk.utils.AttributeMap;
9393
import software.amazon.awssdk.utils.Either;
94+
import software.amazon.awssdk.utils.ScheduledExecutorUtils;
9495
import software.amazon.awssdk.utils.ThreadFactoryBuilder;
9596
import software.amazon.awssdk.utils.Validate;
9697

@@ -222,6 +223,7 @@ private SdkClientConfiguration setOverrides(SdkClientConfiguration configuration
222223

223224
SdkClientConfiguration.Builder builder = configuration.toBuilder();
224225

226+
builder.option(SCHEDULED_EXECUTOR_SERVICE, clientOverrideConfiguration.scheduledExecutorService().orElse(null));
225227
builder.option(EXECUTION_INTERCEPTORS, clientOverrideConfiguration.executionInterceptors());
226228
builder.option(RETRY_POLICY, clientOverrideConfiguration.retryPolicy().orElse(null));
227229
builder.option(ADDITIONAL_HTTP_HEADERS, clientOverrideConfiguration.headers());
@@ -313,7 +315,7 @@ private SdkClientConfiguration finalizeAsyncConfiguration(SdkClientConfiguration
313315
private SdkClientConfiguration finalizeConfiguration(SdkClientConfiguration config) {
314316
RetryPolicy retryPolicy = resolveRetryPolicy(config);
315317
return config.toBuilder()
316-
.option(SCHEDULED_EXECUTOR_SERVICE, resolveScheduledExecutorService())
318+
.option(SCHEDULED_EXECUTOR_SERVICE, resolveScheduledExecutorService(config))
317319
.option(EXECUTION_INTERCEPTORS, resolveExecutionInterceptors(config))
318320
.option(RETRY_POLICY, retryPolicy)
319321
.option(CLIENT_USER_AGENT, resolveClientUserAgent(config, retryPolicy))
@@ -410,9 +412,17 @@ private Executor resolveAsyncFutureCompletionExecutor(SdkClientConfiguration con
410412
* Finalize the internal SDK scheduled executor service that is used for scheduling tasks such
411413
* as async retry attempts and timeout task.
412414
*/
413-
private ScheduledExecutorService resolveScheduledExecutorService() {
414-
return Executors.newScheduledThreadPool(5, new ThreadFactoryBuilder()
415-
.threadNamePrefix("sdk-ScheduledExecutor").build());
415+
private ScheduledExecutorService resolveScheduledExecutorService(SdkClientConfiguration config) {
416+
Supplier<ScheduledExecutorService> defaultScheduledExecutor = () -> {
417+
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5, new ThreadFactoryBuilder()
418+
.threadNamePrefix("sdk-ScheduledExecutor").build());
419+
420+
return executor;
421+
};
422+
423+
return Optional.ofNullable(config.option(SCHEDULED_EXECUTOR_SERVICE))
424+
.map(ScheduledExecutorUtils::unmanagedScheduledExecutor)
425+
.orElseGet(defaultScheduledExecutor);
416426
}
417427

418428
/**

core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/ClientOverrideConfiguration.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424
import java.util.Optional;
2525
import java.util.TreeMap;
26+
import java.util.concurrent.ScheduledExecutorService;
2627
import java.util.function.Consumer;
2728
import software.amazon.awssdk.annotations.SdkPublicApi;
2829
import software.amazon.awssdk.annotations.ToBuilderIgnoreField;
@@ -62,6 +63,7 @@ public final class ClientOverrideConfiguration
6263
private final String defaultProfileName;
6364
private final List<MetricPublisher> metricPublishers;
6465
private final ExecutionAttributes executionAttributes;
66+
private final ScheduledExecutorService scheduledExecutorService;
6567

6668
/**
6769
* Initialize this configuration. Private to require use of {@link #builder()}.
@@ -77,6 +79,7 @@ private ClientOverrideConfiguration(Builder builder) {
7779
this.defaultProfileName = builder.defaultProfileName();
7880
this.metricPublishers = Collections.unmodifiableList(new ArrayList<>(builder.metricPublishers()));
7981
this.executionAttributes = ExecutionAttributes.unmodifiableExecutionAttributes(builder.executionAttributes());
82+
this.scheduledExecutorService = builder.scheduledExecutorService();
8083
}
8184

8285
@Override
@@ -92,7 +95,8 @@ public Builder toBuilder() {
9295
.defaultProfileFile(defaultProfileFile)
9396
.defaultProfileName(defaultProfileName)
9497
.executionAttributes(executionAttributes)
95-
.metricPublishers(metricPublishers);
98+
.metricPublishers(metricPublishers)
99+
.scheduledExecutorService(scheduledExecutorService);
96100
}
97101

98102
/**
@@ -141,6 +145,17 @@ public List<ExecutionInterceptor> executionInterceptors() {
141145
return executionInterceptors;
142146
}
143147

148+
/**
149+
* The optional scheduled executor service that should be used for scheduling tasks such as async retry attempts
150+
* and timeout task.
151+
* <p>
152+
* <b>The SDK will not automatically close the executor when the client is closed. It is the responsibility of the
153+
* user to manually close the executor once all clients utilizing it have been closed.</b>
154+
*/
155+
public Optional<ScheduledExecutorService> scheduledExecutorService() {
156+
return Optional.ofNullable(scheduledExecutorService);
157+
}
158+
144159
/**
145160
* The amount of time to allow the client to complete the execution of an API call. This timeout covers the entire client
146161
* execution except for marshalling. This includes request handler execution, all HTTP requests including retries,
@@ -226,6 +241,7 @@ public String toString() {
226241
.add("advancedOptions", advancedOptions)
227242
.add("profileFile", defaultProfileFile)
228243
.add("profileName", defaultProfileName)
244+
.add("scheduledExecutorService", scheduledExecutorService)
229245
.build();
230246
}
231247

@@ -338,6 +354,20 @@ default Builder retryPolicy(RetryMode retryMode) {
338354

339355
List<ExecutionInterceptor> executionInterceptors();
340356

357+
/**
358+
* Configure the scheduled executor service that should be used for scheduling tasks such as async retry attempts
359+
* and timeout task.
360+
*
361+
* <p>
362+
* <b>The SDK will not automatically close the executor when the client is closed. It is the responsibility of the
363+
* user to manually close the executor once all clients utilizing it have been closed.</b>
364+
*
365+
* @see ClientOverrideConfiguration#scheduledExecutorService()
366+
*/
367+
Builder scheduledExecutorService(ScheduledExecutorService scheduledExecutorService);
368+
369+
ScheduledExecutorService scheduledExecutorService();
370+
341371
/**
342372
* Configure an advanced override option. These values are used very rarely, and the majority of SDK customers can ignore
343373
* them.
@@ -499,6 +529,7 @@ private static final class DefaultClientOverrideConfigurationBuilder implements
499529
private String defaultProfileName;
500530
private List<MetricPublisher> metricPublishers = new ArrayList<>();
501531
private ExecutionAttributes.Builder executionAttributes = ExecutionAttributes.builder();
532+
private ScheduledExecutorService scheduledExecutorService;
502533

503534
@Override
504535
public Builder headers(Map<String, List<String>> headers) {
@@ -561,6 +592,18 @@ public List<ExecutionInterceptor> executionInterceptors() {
561592
return Collections.unmodifiableList(executionInterceptors);
562593
}
563594

595+
@Override
596+
public ScheduledExecutorService scheduledExecutorService()
597+
{
598+
return scheduledExecutorService;
599+
}
600+
601+
@Override
602+
public Builder scheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
603+
this.scheduledExecutorService = scheduledExecutorService;
604+
return this;
605+
}
606+
564607
@Override
565608
public <T> Builder putAdvancedOption(SdkAdvancedClientOption<T> option, T value) {
566609
this.advancedOptions.put(option, value);

core/sdk-core/src/test/java/software/amazon/awssdk/core/client/builder/DefaultClientBuilderTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_FILE_SUPPLIER;
3939
import static software.amazon.awssdk.core.client.config.SdkClientOption.PROFILE_NAME;
4040
import static software.amazon.awssdk.core.client.config.SdkClientOption.RETRY_POLICY;
41+
import static software.amazon.awssdk.core.client.config.SdkClientOption.SCHEDULED_EXECUTOR_SERVICE;
4142
import static software.amazon.awssdk.core.internal.SdkInternalTestAdvancedClientOption.ENDPOINT_OVERRIDDEN_OVERRIDE;
4243

4344
import java.beans.BeanInfo;
@@ -52,6 +53,8 @@
5253
import java.util.List;
5354
import java.util.Map;
5455
import java.util.Optional;
56+
import java.util.concurrent.Executors;
57+
import java.util.concurrent.ScheduledExecutorService;
5558
import java.util.function.Supplier;
5659
import org.assertj.core.api.Assertions;
5760
import org.junit.Before;
@@ -76,6 +79,7 @@
7679
import software.amazon.awssdk.metrics.MetricPublisher;
7780
import software.amazon.awssdk.profiles.ProfileFile;
7881
import software.amazon.awssdk.utils.AttributeMap;
82+
import software.amazon.awssdk.utils.ScheduledExecutorUtils.UnmanagedScheduledExecutorService;
7983
import software.amazon.awssdk.utils.StringInputStream;
8084

8185
/**
@@ -132,6 +136,7 @@ public void overrideConfigurationReturnsSetValues() {
132136
.type(ProfileFile.Type.CONFIGURATION)
133137
.build();
134138
String profileName = "name";
139+
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
135140

136141
ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder()
137142
.executionInterceptors(interceptors)
@@ -148,6 +153,7 @@ public void overrideConfigurationReturnsSetValues() {
148153
.metricPublishers(metricPublishers)
149154
.executionAttributes(executionAttributes)
150155
.putAdvancedOption(ENDPOINT_OVERRIDDEN_OVERRIDE, Boolean.TRUE)
156+
.scheduledExecutorService(scheduledExecutorService)
151157
.build();
152158

153159
TestClientBuilder builder = testClientBuilder().overrideConfiguration(overrideConfig);
@@ -166,6 +172,7 @@ public void overrideConfigurationReturnsSetValues() {
166172
assertThat(builderOverrideConfig.metricPublishers()).isEqualTo(metricPublishers);
167173
assertThat(builderOverrideConfig.executionAttributes().getAttributes()).isEqualTo(executionAttributes.getAttributes());
168174
assertThat(builderOverrideConfig.advancedOption(ENDPOINT_OVERRIDDEN_OVERRIDE)).isEqualTo(Optional.of(Boolean.TRUE));
175+
assertThat(builderOverrideConfig.scheduledExecutorService().get()).isEqualTo(scheduledExecutorService);
169176
}
170177

171178
@Test
@@ -189,6 +196,7 @@ public void overrideConfigurationOmitsUnsetValues() {
189196
assertThat(builderOverrideConfig.metricPublishers()).isEmpty();
190197
assertThat(builderOverrideConfig.executionAttributes().getAttributes()).isEmpty();
191198
assertThat(builderOverrideConfig.advancedOption(ENDPOINT_OVERRIDDEN_OVERRIDE)).isEmpty();
199+
assertThat(builderOverrideConfig.scheduledExecutorService()).isEmpty();
192200
}
193201

194202
@Test
@@ -198,6 +206,7 @@ public void buildIncludesClientOverrides() {
198206
interceptors.add(interceptor);
199207

200208
RetryPolicy retryPolicy = RetryPolicy.builder().build();
209+
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
201210

202211
Map<String, List<String>> headers = new HashMap<>();
203212
List<String> headerValues = new ArrayList<>();
@@ -247,6 +256,7 @@ public void close() {
247256
.metricPublishers(metricPublishers)
248257
.executionAttributes(executionAttributes)
249258
.putAdvancedOption(ENDPOINT_OVERRIDDEN_OVERRIDE, Boolean.TRUE)
259+
.scheduledExecutorService(scheduledExecutorService)
250260
.build();
251261

252262
SdkClientConfiguration config =
@@ -267,6 +277,9 @@ public void close() {
267277
assertThat(config.option(METRIC_PUBLISHERS)).contains(metricPublisher);
268278
assertThat(config.option(EXECUTION_ATTRIBUTES).getAttribute(execAttribute)).isEqualTo("value");
269279
assertThat(config.option(ENDPOINT_OVERRIDDEN)).isEqualTo(Boolean.TRUE);
280+
UnmanagedScheduledExecutorService customScheduledExecutorService =
281+
(UnmanagedScheduledExecutorService) config.option(SCHEDULED_EXECUTOR_SERVICE);
282+
assertThat(customScheduledExecutorService.scheduledExecutorService()).isEqualTo(scheduledExecutorService);
270283
}
271284

272285
@Test

test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/ResourceManagementTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import static software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR;
2424

2525
import java.util.concurrent.ExecutorService;
26+
import java.util.concurrent.ScheduledExecutorService;
27+
2628
import org.junit.jupiter.api.Test;
2729
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
2830
import software.amazon.awssdk.http.SdkHttpClient;
@@ -83,6 +85,16 @@ public void executorFromBuilderNotShutdown() {
8385
verify(executor, never()).shutdownNow();
8486
}
8587

88+
@Test
89+
public void scheduledExecutorFromBuilderNotShutdown() {
90+
ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
91+
92+
asyncClientBuilder().overrideConfiguration(c -> c.scheduledExecutorService(scheduledExecutorService)).build().close();
93+
94+
verify(scheduledExecutorService, never()).shutdown();
95+
verify(scheduledExecutorService, never()).shutdownNow();
96+
}
97+
8698
public ProtocolRestJsonClientBuilder syncClientBuilder() {
8799
return ProtocolRestJsonClient.builder()
88100
.region(Region.US_EAST_1)

0 commit comments

Comments
 (0)