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.SdkClientOption;
27 import software.amazon.awssdk.core.exception.AbortedException;
28 import software.amazon.awssdk.core.exception.ApiCallAttemptTimeoutException;
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 ApiCallAttemptTimeoutTrackingStage<OutputT> implements RequestToResponsePipeline<OutputT> {
44
45 private final RequestPipeline<SdkHttpFullRequest, Response<OutputT>> wrapped;
46 private final Duration apiCallAttemptTimeout;
47 private final ScheduledExecutorService timeoutExecutor;
48
49 public ApiCallAttemptTimeoutTrackingStage(HttpClientDependencies dependencies,
50 RequestPipeline<SdkHttpFullRequest,
51 Response<OutputT>> wrapped) {
52 this.wrapped = wrapped;
53 this.timeoutExecutor = dependencies.clientConfiguration().option(SdkClientOption.SCHEDULED_EXECUTOR_SERVICE);
54 this.apiCallAttemptTimeout = dependencies.clientConfiguration().option(SdkClientOption.API_CALL_ATTEMPT_TIMEOUT);
55 }
56
57
63 @Override
64 public Response<OutputT> execute(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception {
65 try {
66 long timeoutInMillis = resolveTimeoutInMillis(context.requestConfig()::apiCallAttemptTimeout, apiCallAttemptTimeout);
67
68 TimeoutTracker timeoutTracker = timeSyncTaskIfNeeded(timeoutExecutor, timeoutInMillis, Thread.currentThread());
69
70 Response<OutputT> response;
71 try {
72 context.apiCallAttemptTimeoutTracker(timeoutTracker);
73 response = wrapped.execute(request, context);
74 } finally {
75
76
77
78 timeoutTracker.cancel();
79 }
80
81 if (timeoutTracker.hasExecuted()) {
82
83
84
85 Thread.interrupted();
86 }
87 return response;
88
89 } catch (Exception e) {
90 throw translatePipelineException(context, e);
91 }
92 }
93
94
95
96
104 private Exception translatePipelineException(RequestExecutionContext context, Exception e) {
105 if (e instanceof InterruptedException) {
106 return handleInterruptedException(context, (InterruptedException) e);
107 }
108
109
110
111
112
113 if (context.apiCallAttemptTimeoutTracker().hasExecuted()) {
114
115
116 Thread.interrupted();
117 }
118
119 return e;
120 }
121
122
131 private RuntimeException handleInterruptedException(RequestExecutionContext context, InterruptedException e) {
132 if (e instanceof SdkInterruptedException) {
133 ((SdkInterruptedException) e).getResponseStream().ifPresent(r -> invokeSafely(r::close));
134 }
135 if (context.apiCallAttemptTimeoutTracker().hasExecuted()) {
136
137 Thread.interrupted();
138 return generateApiCallAttemptTimeoutException(context);
139 }
140
141 Thread.currentThread().interrupt();
142 return AbortedException.create("Thread was interrupted", e);
143 }
144
145 private ApiCallAttemptTimeoutException generateApiCallAttemptTimeoutException(RequestExecutionContext context) {
146 return ApiCallAttemptTimeoutException.create(
147 resolveTimeoutInMillis(context.requestConfig()::apiCallAttemptTimeout, apiCallAttemptTimeout));
148 }
149 }
150