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.utils.FunctionalUtils.invokeSafely;
20
21 import java.io.IOException;
22 import software.amazon.awssdk.annotations.SdkInternalApi;
23 import software.amazon.awssdk.core.Response;
24 import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
25 import software.amazon.awssdk.core.client.config.SdkClientOption;
26 import software.amazon.awssdk.core.exception.AbortedException;
27 import software.amazon.awssdk.core.exception.ApiCallAttemptTimeoutException;
28 import software.amazon.awssdk.core.exception.SdkInterruptedException;
29 import software.amazon.awssdk.core.internal.http.HttpClientDependencies;
30 import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
31 import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline;
32 import software.amazon.awssdk.core.internal.http.pipeline.RequestToResponsePipeline;
33 import software.amazon.awssdk.http.SdkHttpFullRequest;
34
35
39 @SdkInternalApi
40 public final class TimeoutExceptionHandlingStage<OutputT> implements RequestToResponsePipeline<OutputT> {
41
42 private final HttpClientDependencies dependencies;
43 private final RequestPipeline<SdkHttpFullRequest, Response<OutputT>> requestPipeline;
44
45 public TimeoutExceptionHandlingStage(HttpClientDependencies dependencies, RequestPipeline<SdkHttpFullRequest,
46 Response<OutputT>> requestPipeline) {
47 this.dependencies = dependencies;
48 this.requestPipeline = requestPipeline;
49 }
50
51
74 @Override
75 public Response<OutputT> execute(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception {
76 try {
77 return requestPipeline.execute(request, context);
78 } catch (Exception e) {
79 throw translatePipelineException(context, e);
80 }
81 }
82
83
91 private Exception translatePipelineException(RequestExecutionContext context, Exception e) {
92 if (e instanceof InterruptedException || e instanceof IOException ||
93 e instanceof AbortedException || Thread.currentThread().isInterrupted()) {
94 return handleTimeoutCausedException(context, e);
95 }
96 return e;
97 }
98
99 private Exception handleTimeoutCausedException(RequestExecutionContext context, Exception e) {
100 if (e instanceof SdkInterruptedException) {
101 ((SdkInterruptedException) e).getResponseStream().ifPresent(r -> invokeSafely(r::close));
102 }
103
104 if (isCausedByApiCallTimeout(context)) {
105 return new InterruptedException();
106 }
107
108 if (isCausedByApiCallAttemptTimeout(context)) {
109
110 Thread.interrupted();
111 return generateApiCallAttemptTimeoutException(context);
112 }
113
114 if (e instanceof InterruptedException) {
115 Thread.currentThread().interrupt();
116 return AbortedException.create("Thread was interrupted", e);
117 }
118
119 return e;
120 }
121
122
128 private boolean isCausedByApiCallAttemptTimeout(RequestExecutionContext context) {
129 return context.apiCallAttemptTimeoutTracker().hasExecuted();
130 }
131
132
138 private boolean isCausedByApiCallTimeout(RequestExecutionContext context) {
139 return context.apiCallTimeoutTracker().hasExecuted();
140 }
141
142 private ApiCallAttemptTimeoutException generateApiCallAttemptTimeoutException(RequestExecutionContext context) {
143 return ApiCallAttemptTimeoutException.create(
144 resolveTimeoutInMillis(context.requestConfig()::apiCallAttemptTimeout,
145 dependencies.clientConfiguration().option(SdkClientOption.API_CALL_ATTEMPT_TIMEOUT)));
146 }
147 }
148