2828import static net .tascalate .concurrent .SharedFunctions .wrapCompletionException ;
2929import static net .tascalate .concurrent .SharedFunctions .wrapExecutionException ;
3030
31- import java .util .Arrays ;
3231import java .util .concurrent .Callable ;
3332import java .util .concurrent .CancellationException ;
3433import java .util .concurrent .CompletableFuture ;
@@ -66,31 +65,18 @@ protected AbstractCompletableTask(Executor defaultExecutor, Callable<T> action)
6665 this .task = new StageTransition (action );
6766 }
6867
69- private CompletionStage <?>[] cancellableOrigins ;
70- private Object cancellableOriginsLock = new Object ();
71-
72- protected void resetCancellableOrigins (CompletionStage <?>... origins ) {
73- synchronized (cancellableOriginsLock ) {
74- this .cancellableOrigins = origins ;
75- }
76- }
77-
78- protected void cancelOrigins (boolean mayInterruptIfRunning ) {
79- synchronized (cancellableOriginsLock ) {
80- if (null == cancellableOrigins ) {
81- return ;
82- }
83- Arrays .stream (cancellableOrigins ).forEach (p -> cancelPromise (p , mayInterruptIfRunning ));
84- }
85- }
68+ private volatile CompletionStage <?> intermediateStage ;
8669
8770 abstract void fireTransition (Callable <T > code );
8871
8972 @ Override
9073 public boolean cancel (boolean mayInterruptIfRunning ) {
9174 if (task .cancel (mayInterruptIfRunning )) {
9275 failure (new CancellationException ());
93- cancelOrigins (mayInterruptIfRunning );
76+ CompletionStage <?> s = intermediateStage ;
77+ if (null != s ) {
78+ cancelPromise (s , mayInterruptIfRunning );
79+ }
9480 return true ;
9581 } else {
9682 return false ;
@@ -242,7 +228,7 @@ public <U> Promise<U> thenComposeAsync(Function<? super T, ? extends CompletionS
242228 AbstractCompletableTask <U > nextStage = internalCreateCompletionStage (executor );
243229 // Need to enlist tempStage while it is non-visible outside
244230 // and may not be used to interrupt fn.apply();
245- nextStage .resetCancellableOrigins ( tempStage ) ;
231+ nextStage .intermediateStage = tempStage ;
246232
247233 // We must ALWAYS run through the execution
248234 // of nextStage.task when this nextStage is
@@ -257,10 +243,16 @@ public <U> Promise<U> thenComposeAsync(Function<? super T, ? extends CompletionS
257243 tempStage ,
258244 consumerAsFunction (r -> {
259245 try {
260- CompletionStage <U > returned = fn .apply (r );
261246 // tempStage is completed successfully, so no sense
262247 // to include it in cancellableOrigins
263- // However, nextStage is in progress
248+ nextStage .intermediateStage = null ;
249+ if (nextStage .isDone ()) {
250+ // Was canceled by user code
251+ // Don't need to run anything
252+ return ;
253+ }
254+ CompletionStage <U > returned = fn .apply (r );
255+ // nextStage is in progress
264256 // IMPORTANT: it COULD be shared, but typically is not
265257 // So in very rare case some nasty behavior MAY exist
266258 // if others depends on it
@@ -280,16 +272,15 @@ public <U> Promise<U> thenComposeAsync(Function<? super T, ? extends CompletionS
280272 //} catch (InterruptedException ex) {
281273 //}
282274
283- nextStage .resetCancellableOrigins (returned );
275+ // Always assign before check for cancellation to avoid race
276+ nextStage .intermediateStage = returned ;
284277 if (nextStage .isCancelled ()) {
285- nextStage . cancelOrigins ( true );
278+ cancelPromise ( returned , true );
286279 } else {
287280 // Synchronous, while transition to tempStage is asynchronous already
288281 returned .whenComplete (biConsumer (onResult , onError ));
289282 }
290283 } catch (Throwable e ) {
291- // must-have if fn.apply above failed
292- nextStage .resetCancellableOrigins ((CompletionStage <U >)null );
293284 // no need to check nextStage.isCancelled()
294285 // while there are no origins to cancel
295286 // propagate error immediately
@@ -320,7 +311,7 @@ public Promise<T> exceptionallyComposeAsync(Function<Throwable, ? extends Comple
320311 AbstractCompletableTask <Void > tempStage = internalCreateCompletionStage (executor );
321312 AbstractCompletableTask <T > nextStage = internalCreateCompletionStage (executor );
322313
323- nextStage .resetCancellableOrigins ( tempStage ) ;
314+ nextStage .intermediateStage = tempStage ;
324315
325316 Consumer <? super T > onResult = nextStage .runTransition (Function .identity ());
326317 Consumer <? super Throwable > onError = nextStage .runTransition (AbstractCompletableTask ::forwardException );
@@ -330,15 +321,20 @@ public Promise<T> exceptionallyComposeAsync(Function<Throwable, ? extends Comple
330321 consumerAsFunction (onResult ),
331322 consumerAsFunction (error -> {
332323 try {
324+ nextStage .intermediateStage = null ;
325+ if (nextStage .isDone ()) {
326+ // Was canceled by user code
327+ // Don't need to run anything
328+ return ;
329+ }
333330 CompletionStage <T > returned = fn .apply (error );
334- nextStage .resetCancellableOrigins ( returned ) ;
331+ nextStage .intermediateStage = returned ;
335332 if (nextStage .isCancelled ()) {
336- nextStage . cancelOrigins ( true );
333+ cancelPromise ( returned , true );
337334 } else {
338335 returned .whenComplete (biConsumer (onResult , onError ));
339336 }
340337 } catch (Throwable e ) {
341- nextStage .resetCancellableOrigins ((CompletionStage <T >)null );
342338 // In JDK 12 CompletionStage.composeExceptionally[Async] uses *.handle[Async]
343339 // So overwrite returned error with the latest one - as in handle()
344340 e .addSuppressed (error );
0 commit comments