16
16
package software .amazon .awssdk .core .internal .http .pipeline .stages ;
17
17
18
18
import static org .assertj .core .api .Assertions .assertThat ;
19
+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
19
20
import static org .mockito .ArgumentMatchers .any ;
20
21
import static org .mockito .ArgumentMatchers .anyLong ;
21
22
import static org .mockito .ArgumentMatchers .eq ;
22
23
import static org .mockito .Mockito .doThrow ;
23
24
import static org .mockito .Mockito .mock ;
24
25
import static org .mockito .Mockito .never ;
26
+ import static org .mockito .Mockito .spy ;
25
27
import static org .mockito .Mockito .times ;
26
28
import static org .mockito .Mockito .verify ;
27
29
import static org .mockito .Mockito .when ;
33
35
import java .time .Duration ;
34
36
import java .util .concurrent .CompletableFuture ;
35
37
import java .util .concurrent .ExecutorService ;
38
+ import java .util .concurrent .Executors ;
36
39
import java .util .concurrent .RejectedExecutionException ;
37
40
import java .util .concurrent .ScheduledExecutorService ;
38
41
import java .util .concurrent .ScheduledFuture ;
58
61
import software .amazon .awssdk .http .async .AsyncExecuteRequest ;
59
62
import software .amazon .awssdk .http .async .SdkAsyncHttpClient ;
60
63
import software .amazon .awssdk .metrics .MetricCollector ;
64
+ import software .amazon .awssdk .utils .CompletableFutureUtils ;
65
+ import software .amazon .awssdk .utils .ThreadFactoryBuilder ;
61
66
import utils .ValidSdkObjects ;
62
67
63
68
@ RunWith (MockitoJUnitRunner .class )
@@ -157,7 +162,7 @@ public void testExecute_contextContainsMetricCollector_addsChildToExecuteRequest
157
162
}
158
163
159
164
@ Test
160
- public void execute_futureCompletionExecutorRejectsWhenCompleteAsync_futureCompletedSynchronously () {
165
+ public void execute_handlerFutureCompletedNormally_futureCompletionExecutorRejectsWhenCompleteAsync_futureCompletedSynchronously () {
161
166
ExecutorService mockExecutor = mock (ExecutorService .class );
162
167
doThrow (new RejectedExecutionException ("Busy" )).when (mockExecutor ).execute (any (Runnable .class ));
163
168
@@ -169,7 +174,8 @@ public void execute_futureCompletionExecutorRejectsWhenCompleteAsync_futureCompl
169
174
HttpClientDependencies dependencies = HttpClientDependencies .builder ().clientConfiguration (config ).build ();
170
175
171
176
TransformingAsyncResponseHandler mockHandler = mock (TransformingAsyncResponseHandler .class );
172
- when (mockHandler .prepare ()).thenReturn (CompletableFuture .completedFuture (null ));
177
+ CompletableFuture prepareFuture = new CompletableFuture ();
178
+ when (mockHandler .prepare ()).thenReturn (prepareFuture );
173
179
174
180
stage = new MakeAsyncHttpRequestStage <>(mockHandler , dependencies );
175
181
@@ -179,10 +185,58 @@ public void execute_futureCompletionExecutorRejectsWhenCompleteAsync_futureCompl
179
185
CompletableFuture executeFuture = stage .execute (requestFuture , requestContext ());
180
186
181
187
long testThreadId = Thread .currentThread ().getId ();
182
- executeFuture .whenComplete ((r , t ) -> assertThat (Thread .currentThread ().getId ()).isEqualTo (testThreadId )).join ();
188
+ CompletableFuture afterWhenComplete =
189
+ executeFuture .whenComplete ((r , t ) -> assertThat (Thread .currentThread ().getId ()).isEqualTo (testThreadId ));
190
+
191
+ prepareFuture .complete (null );
192
+
193
+ afterWhenComplete .join ();
194
+
183
195
verify (mockExecutor ).execute (any (Runnable .class ));
184
196
}
185
197
198
+ @ Test
199
+ public void execute_handlerFutureCompletedExceptionally_doesNotAttemptSynchronousComplete () {
200
+ String threadNamePrefix = "async-handle-test" ;
201
+ ExecutorService mockExecutor = Executors .newSingleThreadExecutor (
202
+ new ThreadFactoryBuilder ().threadNamePrefix (threadNamePrefix ).build ());
203
+
204
+ SdkClientConfiguration config =
205
+ SdkClientConfiguration .builder ()
206
+ .option (SdkAdvancedAsyncClientOption .FUTURE_COMPLETION_EXECUTOR , mockExecutor )
207
+ .option (ASYNC_HTTP_CLIENT , sdkAsyncHttpClient )
208
+ .build ();
209
+ HttpClientDependencies dependencies = HttpClientDependencies .builder ().clientConfiguration (config ).build ();
210
+
211
+ TransformingAsyncResponseHandler mockHandler = mock (TransformingAsyncResponseHandler .class );
212
+ CompletableFuture prepareFuture = spy (new CompletableFuture ());
213
+ when (mockHandler .prepare ()).thenReturn (prepareFuture );
214
+
215
+ stage = new MakeAsyncHttpRequestStage <>(mockHandler , dependencies );
216
+
217
+ CompletableFuture <SdkHttpFullRequest > requestFuture = CompletableFuture .completedFuture (
218
+ ValidSdkObjects .sdkHttpFullRequest ().build ());
219
+
220
+ CompletableFuture executeFuture = stage .execute (requestFuture , requestContext ());
221
+
222
+ try {
223
+ CompletableFuture afterHandle =
224
+ executeFuture .handle ((r , t ) -> assertThat (Thread .currentThread ().getName ()).startsWith (threadNamePrefix ));
225
+
226
+ prepareFuture .completeExceptionally (new RuntimeException ("parse error" ));
227
+
228
+ afterHandle .join ();
229
+
230
+ assertThatThrownBy (executeFuture ::join )
231
+ .hasCauseInstanceOf (RuntimeException .class )
232
+ .hasMessageContaining ("parse error" );
233
+
234
+ verify (prepareFuture , times (0 )).whenComplete (any ());
235
+ } finally {
236
+ mockExecutor .shutdown ();
237
+ }
238
+ }
239
+
186
240
private HttpClientDependencies clientDependencies (Duration timeout ) {
187
241
SdkClientConfiguration configuration = SdkClientConfiguration .builder ()
188
242
.option (SdkAdvancedAsyncClientOption .FUTURE_COMPLETION_EXECUTOR , Runnable ::run )
0 commit comments