@@ -248,40 +248,49 @@ public <U> Promise<U> thenComposeAsync(Function<? super T, ? extends CompletionS
248248 addCallbacks (
249249 tempStage ,
250250 consumerAsFunction (r -> {
251- CompletionStage <U > returned = fn .apply (r );
252- // tempStage is completed successfully, so no sense
253- // to include it in cancellableOrigins
254- // However, nextStage is in progress
255- // IMPORTANT: it COULD be shared, but typically is not
256- // So in very rare case some nasty behavior MAY exist
257- // if others depends on it
258-
259- // TEST: There is a race when fn.apply(r) is completed
260- // normally and nextStage is cancelled before returned is set
261- // as its nextStage's cancellableOrigins. In this case,
262- // execution of returned continues as nextStage cannot be
263- // cancelled for a second time. However, completion stages after
264- // nextStage are completed exceptionally (correctly) since when
265- // moveToNextStage is executed nextStage is already completed
266- // (cancelled) from cancel(...) -> onError(...). In order to
267- // cancel returned here, I think you need to know whether
268- // nextStage might have been interrupted.
269- // try {
270- // Thread.sleep(100);
271- //} catch (InterruptedException ex) {
272- //}
273-
274- nextStage .resetCancellableOrigins (returned );
275- if (nextStage .isCancelled ()) {
276- nextStage .cancelOrigins (true );
277- } else {
278- returned .whenComplete (moveToNextStage );
251+ try {
252+ CompletionStage <U > returned = fn .apply (r );
253+ // tempStage is completed successfully, so no sense
254+ // to include it in cancellableOrigins
255+ // However, nextStage is in progress
256+ // IMPORTANT: it COULD be shared, but typically is not
257+ // So in very rare case some nasty behavior MAY exist
258+ // if others depends on it
259+
260+ // TEST: There is a race when fn.apply(r) is completed
261+ // normally and nextStage is cancelled before returned is set
262+ // as its nextStage's cancellableOrigins. In this case,
263+ // execution of returned continues as nextStage cannot be
264+ // cancelled for a second time. However, completion stages after
265+ // nextStage are completed exceptionally (correctly) since when
266+ // moveToNextStage is executed nextStage is already completed
267+ // (cancelled) from cancel(...) -> onError(...). In order to
268+ // cancel returned here, I think you need to know whether
269+ // nextStage might have been interrupted.
270+ // try {
271+ // Thread.sleep(100);
272+ //} catch (InterruptedException ex) {
273+ //}
274+
275+ nextStage .resetCancellableOrigins (returned );
276+ if (nextStage .isCancelled ()) {
277+ nextStage .cancelOrigins (true );
278+ } else {
279+ returned .whenComplete (moveToNextStage );
280+ }
281+ } catch (Throwable ex ) {
282+ // must-have if fn.apply above failed
283+ nextStage .resetCancellableOrigins ((CompletionStage <U >)null );
284+ // no need to check nextStage.isCancelled()
285+ // while there are no origins to cancel
286+ // propagate error immediately
287+ moveToNextStage .accept (null , ex );
279288 }
280289 }),
281290 e -> {
282291 moveToNextStage .accept (null , e );
283292 return null ;
284- }, /* must-have if fn.apply above failed */
293+ },
285294 executor
286295 );
287296
0 commit comments