All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.tascalate.concurrent.AbstractCompletableTask Maven / Gradle / Ivy

Go to download

Implementation of blocking (IO-Bound) cancellable java.util.concurrent.CompletionStage and related extensions to java.util.concurrent.ExecutorService-s

There is a newer version: 0.9.8
Show newest version
/**
 * Original work: copyright 2009-2015 Lukáš Křečan 
 * https://github.com/lukas-krecan/completion-stage
 * 
 * This class is based on the work create by Lukáš Křečan 
 * under the Apache License, Version 2.0. Please see 
 * https://github.com/lukas-krecan/completion-stage/blob/completion-stage-0.0.9/src/main/java/net/javacrumbs/completionstage/SimpleCompletionStage.java
 * 
 * Modified work: copyright 2015-2019 Valery Silaev (http://vsilaev.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.tascalate.concurrent;

import static net.tascalate.concurrent.SharedFunctions.cancelPromise;
import static net.tascalate.concurrent.SharedFunctions.unwrapCompletionException;
import static net.tascalate.concurrent.SharedFunctions.unwrapExecutionException;
import static net.tascalate.concurrent.SharedFunctions.wrapCompletionException;
import static net.tascalate.concurrent.SharedFunctions.wrapExecutionException;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Base superclass for both root and intermediate {@link Promise}-s that
 * represent blocking long-running tasks
 * 
 * @author vsilaev
 *
 * @param 
 *   a type of the successfully executed task result   
 */
abstract class AbstractCompletableTask extends PromiseAdapter implements Promise {

    private final CallbackRegistry callbackRegistry = new CallbackRegistry<>();
    protected final RunnableFuture task;
    protected final Callable action;

    protected AbstractCompletableTask(Executor defaultExecutor, Callable action) {
        super(defaultExecutor);
        this.action = action;
        this.task = new StageTransition(action);
    }
    
    private CompletionStage[] cancellableOrigins;
    private Object cancellableOriginsLock = new Object();
    
    protected void resetCancellableOrigins(CompletionStage... origins) {
        synchronized (cancellableOriginsLock) {
            this.cancellableOrigins = origins; 
        }
    }
    
    protected void cancelOrigins(boolean mayInterruptIfRunning) {
        synchronized (cancellableOriginsLock) {
            if (null == cancellableOrigins) {
                return;
            }
            Arrays.stream(cancellableOrigins).forEach(p -> cancelPromise(p, mayInterruptIfRunning)); 
        }
    }

    abstract void fireTransition(Callable code);

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        if (task.cancel(mayInterruptIfRunning)) {
            onError(new CancellationException());
            cancelOrigins(mayInterruptIfRunning);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean isCancelled() {
        return task.isCancelled();
    }

    @Override
    public boolean isDone() {
        return task.isDone();
    }

    @Override
    public T get() throws InterruptedException, ExecutionException {
        try {
            return task.get();
        } catch (ExecutionException ex) {
            throw rewrapExecutionException(ex);
        }
    }

    @Override
    public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        try {
            return task.get(timeout, unit);
        } catch (ExecutionException ex) {
            throw rewrapExecutionException(ex);
        }
    }

    boolean onSuccess(T result) {
        return callbackRegistry.success(result);
    }

    boolean onError(Throwable ex) {
        return callbackRegistry.failure(ex);
    }

    class StageTransition extends FutureTask {
        StageTransition(Callable callable) {
            super(callable);
        }

        @Override
        protected void set(T v) {
            super.set(v);
            onSuccess(v);
        };

        @Override
        protected void setException(Throwable t) {
            super.setException(t);
            onError(t);
        };
    }

    @Override
    public  Promise thenApplyAsync(Function fn, Executor executor) {
        AbstractCompletableTask nextStage = internalCreateCompletionStage(executor);
        addCallbacks(nextStage, fn, executor);
        return nextStage;
    }

    @Override
    public Promise thenAcceptAsync(Consumer action, Executor executor) {
        return thenApplyAsync(consumerAsFunction(action), executor);
    }

    @Override
    public Promise thenRunAsync(Runnable action, Executor executor) {
        return thenApplyAsync(runnableAsFunction(action), executor);
    }

    @Override
    public  Promise thenCombineAsync(CompletionStage other,
                                              BiFunction fn, 
                                              Executor executor) {

        return thenCompose(result1 -> other.thenApplyAsync(result2 -> fn.apply(result1, result2), executor));
    }

    @Override
    public  Promise thenAcceptBothAsync(CompletionStage other,
                                                 BiConsumer action, 
                                                 Executor executor) {
        return thenCombineAsync(other,
                // transform BiConsumer to BiFunction
                (t, u) -> {
                    action.accept(t, u);
                    return null;
                }, executor);
    }

    @Override
    public Promise runAfterBothAsync(CompletionStage other, 
                                           Runnable action, 
                                           Executor executor) {
        return thenCombineAsync(other,
                // transform Runnable to BiFunction
                (t, r) -> {
                    action.run();
                    return null;
                }, executor);
    }

    @Override
    public  Promise applyToEitherAsync(CompletionStage other, 
                                             Function fn,
                                             Executor executor) {

        return doApplyToEitherAsync(this, other, fn, executor);
    }

    @Override
    public Promise acceptEitherAsync(CompletionStage other,
                                           Consumer action,
                                           Executor executor) {

        return applyToEitherAsync(other, consumerAsFunction(action), executor);
    }

    @Override
    public Promise runAfterEitherAsync(CompletionStage other, 
                                             Runnable action, 
                                             Executor executor) {
        
        return doApplyToEitherAsync(this, other, runnableAsFunction(action), executor);
    }

    @Override
    public  Promise thenComposeAsync(Function> fn, Executor executor) {

        AbstractCompletableTask tempStage = internalCreateCompletionStage(executor);
        AbstractCompletableTask nextStage = internalCreateCompletionStage(executor);
        // Need to enlist tempStage while it is non-visible outside
        // and may not be used to interrupt fn.apply();
        nextStage.resetCancellableOrigins(tempStage);

        // We must ALWAYS run through the execution
        // of nextStage.task when this nextStage is
        // exposed to the client, even in a "trivial" case:
        // Success path, just return value
        Consumer onResult = nextStage.runTransition(Function.identity());
        // Failure path, just re-throw exception
        Consumer onError = nextStage.runTransition(AbstractCompletableTask::forwardException);

        // Important -- tempStage is the target here
        addCallbacks(
            tempStage, 
            consumerAsFunction(r -> {
                try {
                    CompletionStage returned = fn.apply(r);
                    // tempStage is completed successfully, so no sense
                    // to include it in cancellableOrigins
                    // However, nextStage is in progress
                    // IMPORTANT: it COULD be shared, but typically is not
                    // So in very rare case some nasty behavior MAY exist 
                    // if others depends on it
                    
                    // TEST: There is a race when fn.apply(r) is completed
                    // normally and nextStage is cancelled before returned is set
                    // as its nextStage's cancellableOrigins. In this case,
                    // execution of returned continues as nextStage cannot be
                    // cancelled for a second time. However, completion stages after
                    // nextStage are completed exceptionally (correctly) since when
                    // moveToNextStage is executed nextStage is already completed
                    // (cancelled) from cancel(...) -> onError(...). In order to
                    // cancel returned here, I think you need to know whether
                    // nextStage might have been interrupted.
                    // try {
                    //    Thread.sleep(100);
                    //} catch (InterruptedException ex) {
                    //}
                    
                    nextStage.resetCancellableOrigins(returned);
                    if (nextStage.isCancelled()) {
                        nextStage.cancelOrigins(true);
                    } else {
                        // Synchronous, while transition to tempStage is asynchronous already
                        returned.whenComplete(biConsumer(onResult, onError));
                    }
                } catch (Throwable ex) {
                    // must-have if fn.apply above failed 
                    nextStage.resetCancellableOrigins((CompletionStage)null);
                    // no need to check nextStage.isCancelled()
                    // while there are no origins to cancel
                    // propagate error immediately
                    // synchronous, while transition to tempStage is asynchronous already
                    onError.accept(ex);  
                }
            }), 
            consumerAsFunction(onError),
            executor
        );

        return nextStage;
    }
    
    private  Consumer runTransition(Function converter) {
        return u -> fireTransition(() -> converter.apply(u)); 
    }

    @Override
    public Promise exceptionally(Function fn) {
        AbstractCompletableTask nextStage = internalCreateCompletionStage(getDefaultExecutor());
        addCallbacks(nextStage, Function.identity(), fn, SAME_THREAD_EXECUTOR);
        return nextStage;
    }

    @Override
    public Promise whenCompleteAsync(BiConsumer action, Executor executor) {
        AbstractCompletableTask nextStage = internalCreateCompletionStage(executor);
        addCallbacks(
            nextStage, 
            result -> {
                try {
                    action.accept(result, null);
                } catch (Throwable e) {
                    // CompletableFuture wraps exception here
                    // Copying this behavior                    
                    return forwardException(e);
                }
                return result;
            }, 
            // exceptions are handled in regular way
            failure -> {
                try {
                    action.accept(null, failure);
                } catch (Throwable e) {
                    // CompletableFuture does not override exception here
                    // unlike as in handle[Async](BiFunction)
                    // Preserve this behavior, but let us add at least 
                    // suppressed exception
                    failure.addSuppressed(e);
                }
                return forwardException(failure);
            }, 
            executor
        );
        return nextStage;
    }

    @Override
    public  Promise handleAsync(BiFunction fn, Executor executor) {
        AbstractCompletableTask nextStage = internalCreateCompletionStage(executor);
        addCallbacks(
            nextStage, 
            result -> {
                try {
                    return fn.apply(result, null);
                } catch (Throwable e) {
                    // CompletableFuture wraps exception here
                    // Copying this behavior
                    return forwardException(e);
                }
            },
            // exceptions are handled in regular way
            failure -> {
                try {
                    return fn.apply(null, failure);
                } catch (Throwable e) {
                    // CompletableFuture handle[Async](BiFunction)
                    // allows to overwrite exception for resulting stage.
                    // Copying this behavior
                    return forwardException(e);
                }
            },
            executor
        );
        return nextStage;
    }

    @Override
    public CompletableFuture toCompletableFuture() {
        CompletableFuture completableFuture = new CompletableFuture<>();
        // nextStage is CompletableFuture rather than AbstractCompletableTask
        // so trigger completion on ad-hoc runnable rather than on
        // nextStage.task
        Consumer> setup = c -> {
            try {
                c.call();
            } catch (Throwable ex) {
                completableFuture.completeExceptionally(ex);
            }
        };
        addCallbacks(
            setup, 
            consumerAsFunction(completableFuture::complete),
            consumerAsFunction(completableFuture::completeExceptionally), 
            SAME_THREAD_EXECUTOR
        );
        return completableFuture;
    }

    abstract protected  AbstractCompletableTask createCompletionStage(Executor executor);

    /**
     * This method exists just to reconcile generics when called from
     * {@link #runAfterEitherAsync} which has unexpected type of parameter
     * "other". The alternative is to ignore compiler warning.
     */
    private  Promise doApplyToEitherAsync(CompletionStage first,
                                                   CompletionStage second, 
                                                   Function fn, 
                                                   Executor executor) {

        AbstractCompletableTask nextStage = internalCreateCompletionStage(executor);

        // Next stage is not exposed to the client, so we can
        // short-circuit its initiation - just fire callbacks
        // without task execution (unlike as in other methods,
        // event in thenComposeAsync with its ad-hoc execution)

        // In certain sense, nextStage here is bogus: neither
        // of Future-defined methods are functional.
        BiConsumer action = (result, failure) -> {
            if (failure == null) {
                nextStage.onSuccess(result);
            } else {
                nextStage.onError(forwardException(failure));
            }
        };
        // only the first result is accepted by completion stage,
        // the other one is ignored
        first.whenComplete(action);
        second.whenComplete(action);

        return nextStage.thenApplyAsync(fn, executor);
    }

    private  AbstractCompletableTask internalCreateCompletionStage(Executor executor) {
        // Preserve default async executor, or use user-supplied executor as default
        // But don't let SAME_THREAD_EXECUTOR to be a default async executor
        return createCompletionStage(executor == SAME_THREAD_EXECUTOR ? getDefaultExecutor() : executor);
    }

    private static  Function consumerAsFunction(Consumer action) {
        return result -> {
            action.accept(result);
            return null;
        };
    }

    private static  Function runnableAsFunction(Runnable action) {
        return result -> {
            action.run();
            return null;
        };
    }
    
    private static  BiConsumer biConsumer(Consumer onResult, Consumer onError) {
        return (u, v) -> {
            if (null == v) {
                onResult.accept(u);
            } else {
                onError.accept(v);
            }
        };
    }

    private static  U forwardException(Throwable e) {
        throw wrapCompletionException(e);
    }
    
    private static ExecutionException rewrapExecutionException(ExecutionException ex) {
        return wrapExecutionException( unwrapCompletionException(unwrapExecutionException(ex)) );
    }    

    private  void addCallbacks(AbstractCompletableTask targetStage,
                                  Function successCallback, 
                                  Executor executor) {
        
        addCallbacks(targetStage, successCallback, AbstractCompletableTask::forwardException, executor);
    }

    private  void addCallbacks(AbstractCompletableTask targetStage,
                                  Function successCallback, 
                                  Function failureCallback,
                                  Executor executor) {
        
        addCallbacks(targetStage::fireTransition, successCallback, failureCallback, executor);
    }

    private  void addCallbacks(Consumer> stageTransition,
                                  Function successCallback, 
                                  Function failureCallback,
                                  Executor executor) {
        
        callbackRegistry.addCallbacks(stageTransition, successCallback, failureCallback, executor);
    }

}