net.tascalate.concurrent.AbstractCompletableTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of net.tascalate.concurrent.lib Show documentation
Show all versions of net.tascalate.concurrent.lib Show documentation
Implementation of blocking (IO-Bound) cancellable java.util.concurrent.CompletionStage
and related extensions to java.util.concurrent.ExecutorService-s
/**
* 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-2017 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 java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
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);
}
abstract Runnable setupTransition(Callable code);
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (task.cancel(mayInterruptIfRunning)) {
onError(new CancellationException());
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 nextStage = internalCreateCompletionStage(executor);
AbstractCompletableTask tempStage = internalCreateCompletionStage(executor);
// 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
// Failure path, just re-throw exception
Executor helperExecutor = SAME_THREAD_EXECUTOR;
BiConsumer moveToNextStage = (r, e) -> {
if (null == e)
runDirectly(nextStage, Function.identity(), r, helperExecutor);
else
runDirectly(nextStage, AbstractCompletableTask::forwardException, e, helperExecutor);
};
// Important -- tempStage is the target here
addCallbacks(
tempStage,
consumerAsFunction(r -> fn.apply(r).whenComplete(moveToNextStage)),
e -> { moveToNextStage.accept(null, e); return null; }, /* must-have if fn.apply above failed */
executor
);
return nextStage;
}
@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(getDefaultExecutor());
addCallbacks(
nextStage,
result -> {
action.accept(result, null);
return result;
},
failure -> {
try {
action.accept(null, failure);
return forwardException(failure);
} catch (Throwable e) {
return forwardException(e);
}
},
executor
);
return nextStage;
}
@Override
public Promise handleAsync(BiFunction fn, Executor executor) {
AbstractCompletableTask nextStage = internalCreateCompletionStage(executor);
addCallbacks(
nextStage,
result -> fn.apply(result, null),
// exceptions are treated as success
error -> fn.apply(null, error), 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
Function, Runnable> setup = c -> () -> {
try {
c.call();
} catch (final 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(wrapException(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 U forwardException(Throwable e) {
throw wrapException(e);
}
private static CompletionException wrapException(Throwable e) {
if (e instanceof CompletionException) {
return (CompletionException) e;
} else {
return new CompletionException(e);
}
}
private static ExecutionException rewrapExecutionException(ExecutionException ex) {
if (ex.getCause() instanceof CompletionException) {
Throwable completionExceptionReason = ex.getCause().getCause();
if (null != completionExceptionReason) {
return new ExecutionException(ex.getMessage(), completionExceptionReason);
}
}
return 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::setupTransition, successCallback, failureCallback, executor);
}
private void addCallbacks(Function, ? extends Runnable> targetSetup,
Function successCallback,
Function failureCallback,
Executor executor) {
callbackRegistry.addCallbacks(targetSetup, successCallback, failureCallback, executor);
}
private static void runDirectly(AbstractCompletableTask targetStage,
Function callback,
S value,
Executor executor) {
CallbackRegistry.callCallback(targetStage::setupTransition, callback, value, executor);
}
}