1
15
16 package software.amazon.awssdk.core.internal.http.pipeline.stages;
17
18 import static software.amazon.awssdk.core.internal.http.timers.TimerUtils.resolveTimeoutInMillis;
19 import static software.amazon.awssdk.core.internal.http.timers.TimerUtils.timeSyncTaskIfNeeded;
20 import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
21
22 import java.time.Duration;
23 import java.util.concurrent.ScheduledExecutorService;
24 import software.amazon.awssdk.annotations.SdkInternalApi;
25 import software.amazon.awssdk.core.Response;
26 import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
27 import software.amazon.awssdk.core.client.config.SdkClientOption;
28 import software.amazon.awssdk.core.exception.AbortedException;
29 import software.amazon.awssdk.core.exception.ApiCallTimeoutException;
30 import software.amazon.awssdk.core.exception.SdkInterruptedException;
31 import software.amazon.awssdk.core.internal.http.HttpClientDependencies;
32 import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
33 import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline;
34 import software.amazon.awssdk.core.internal.http.pipeline.RequestToResponsePipeline;
35 import software.amazon.awssdk.core.internal.http.timers.SyncTimeoutTask;
36 import software.amazon.awssdk.core.internal.http.timers.TimeoutTracker;
37 import software.amazon.awssdk.http.SdkHttpFullRequest;
38
39
42 @SdkInternalApi
43 public final class ApiCallTimeoutTrackingStage<OutputT> implements RequestToResponsePipeline<OutputT> {
44 private final RequestPipeline<SdkHttpFullRequest, Response<OutputT>> wrapped;
45 private final SdkClientConfiguration clientConfig;
46 private final ScheduledExecutorService timeoutExecutor;
47 private final Duration apiCallTimeout;
48
49 public ApiCallTimeoutTrackingStage(HttpClientDependencies dependencies,
50 RequestPipeline<SdkHttpFullRequest, Response<OutputT>> wrapped) {
51 this.wrapped = wrapped;
52 this.clientConfig = dependencies.clientConfiguration();
53 this.timeoutExecutor = dependencies.clientConfiguration().option(SdkClientOption.SCHEDULED_EXECUTOR_SERVICE);
54 this.apiCallTimeout = clientConfig.option(SdkClientOption.API_CALL_TIMEOUT);
55 }
56
57 @Override
58 public Response<OutputT> execute(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception {
59 try {
60 return executeWithTimer(request, context);
61 } catch (Exception e) {
62 throw translatePipelineException(context, e);
63 }
64 }
65
66
72 private Response<OutputT> executeWithTimer(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception {
73 long timeoutInMillis = resolveTimeoutInMillis(context.requestConfig()::apiCallTimeout, apiCallTimeout);
74
75 TimeoutTracker timeoutTracker = timeSyncTaskIfNeeded(timeoutExecutor, timeoutInMillis, Thread.currentThread());
76
77 Response<OutputT> response;
78 try {
79 context.apiCallTimeoutTracker(timeoutTracker);
80 response = wrapped.execute(request, context);
81 } finally {
82
83
84
85 timeoutTracker.cancel();
86 }
87
88 if (timeoutTracker.hasExecuted()) {
89
90
91
92 Thread.interrupted();
93 }
94 return response;
95 }
96
97
105 private Exception translatePipelineException(RequestExecutionContext context, Exception e) {
106 if (e instanceof InterruptedException) {
107 return handleInterruptedException(context, (InterruptedException) e);
108 }
109
110
111
112
113
114 if (context.apiCallTimeoutTracker().hasExecuted()) {
115
116
117 Thread.interrupted();
118 }
119
120 return e;
121 }
122
123
132 private RuntimeException handleInterruptedException(RequestExecutionContext context, InterruptedException e) {
133 if (e instanceof SdkInterruptedException) {
134 ((SdkInterruptedException) e).getResponseStream().ifPresent(r -> invokeSafely(r::close));
135 }
136 if (context.apiCallTimeoutTracker().hasExecuted()) {
137
138 Thread.interrupted();
139 return generateApiCallTimeoutException(context);
140 }
141
142 Thread.currentThread().interrupt();
143 return AbortedException.create("Thread was interrupted", e);
144 }
145
146 private ApiCallTimeoutException generateApiCallTimeoutException(RequestExecutionContext context) {
147 return ApiCallTimeoutException.create(
148 resolveTimeoutInMillis(context.requestConfig()::apiCallTimeout, apiCallTimeout));
149 }
150 }
151