/*
* Copyright 2012 LinkedIn, Inc
*
* 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 com.linkedin.parseq;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.linkedin.parseq.function.Action;
import com.linkedin.parseq.function.Consumer1;
import com.linkedin.parseq.function.Failure;
import com.linkedin.parseq.function.Function1;
import com.linkedin.parseq.function.Success;
import com.linkedin.parseq.function.Try;
import com.linkedin.parseq.internal.ArgumentUtil;
import com.linkedin.parseq.internal.TimeUnitHelper;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.PromisePropagator;
import com.linkedin.parseq.promise.PromiseTransformer;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import com.linkedin.parseq.retry.RetriableTask;
import com.linkedin.parseq.retry.RetryPolicy;
import com.linkedin.parseq.retry.RetryPolicyBuilder;
import com.linkedin.parseq.trace.ShallowTrace;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.Trace;
import com.linkedin.parseq.trace.TraceBuilder;
/**
* A task represents a deferred execution that also contains its resulting
* value. In addition, tasks include tracing information that can be
* used with various trace printers.
* <p>
* Tasks should be run using an {@link Engine}. They should not be run directly.
* <p>
*
* @author Chris Pettitt (cpettitt@linkedin.com)
* @author Jaroslaw Odzga (jodzga@linkedin.com)
*/
public interface Task<T> extends Promise<T>, Cancellable {
static final Logger LOGGER = LoggerFactory.getLogger(Task.class);
static final TaskDescriptor _taskDescriptor = TaskDescriptorFactory.getTaskDescriptor();
//------------------- interface definition -------------------
/**
* Returns the name of this task.
*
* @return the name of this task
*/
public String getName();
/**
* Returns the priority for this task.
*
* @return the priority for this task.
*/
int getPriority();
/**
* Overrides the priority for this task. Higher priority tasks will be
* executed before lower priority tasks in the same context. In most cases,
* the default priority is sufficient.
* <p>
* The default priority is 0. Use {@code priority < 0} to make a task
* lower priority and {@code priority > 0} to make a task higher
* priority.
* <p>
* If the task has already started execution the priority cannot be
* changed.
*
* @param priority the new priority for the task.
* @return {@code true} if the priority was set; otherwise {@code false}.
* @throws IllegalArgumentException if the priority is out of range
* @see Priority
*/
boolean setPriority(int priority);
/**
* Allows adding {@code String} representation of value computed by this task to trace.
* When this task is finished successfully, value will be converted to String using given
* serializer and it will be included in this task's trace.
* <p>
* Failures are automatically included in a trace.
* @param serializer serialized used for converting result of this task
* to String that will be included in this task's trace.
*/
void setTraceValueSerializer(Function<T, String> serializer);
/**
* Attempts to run the task with the given context. This method is
* reserved for use by {@link Engine} and {@link Context}.
*
* @param context the context to use while running this step
* @param parent the parent of this task
* @param predecessors that lead to the execution of this task
*/
void contextRun(Context context, Task<?> parent, Collection<Task<?>> predecessors);
/**
* Returns the ShallowTrace for this task. The ShallowTrace will be
* a point-in-time snapshot and may change over time until the task is
* completed.
*
* @return the ShallowTrace related to this task
*/
ShallowTrace getShallowTrace();
/**
* Returns the Trace for this task. The Trace will be a point-in-time snapshot
* and may change over time until the task is completed.
*
* @return the Trace related to this task
*/
Trace getTrace();
/**
* Unique identifier of the task.
* @return unique identifier of the task.
*/
Long getId();
ShallowTraceBuilder getShallowTraceBuilder();
TraceBuilder getTraceBuilder();
//------------------- default methods -------------------
default <R> Task<R> apply(final String desc, final PromisePropagator<T, R> propagator) {
return FusionTask.create(desc, this, propagator);
}
/**
* Creates a new task by applying a function to the successful result of this task.
* Returned task will complete with value calculated by a function.
* <blockquote><pre>
* Task{@code <String>} hello = Task.value("Hello World");
*
* // this task will complete with value 11
* Task{@code <Integer>} length = hello.map("length", s {@code ->} s.length());
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/map-1.png" height="90" width="296"/>
* <p>
* If this task is completed with an exception then the new task will also complete
* with that exception.
* <blockquote><pre>
* Task{@code <String>} failing = Task.callable("hello", () {@code ->} {
* return "Hello World".substring(100);
* });
*
* // this task will fail with java.lang.StringIndexOutOfBoundsException
* Task{@code <Integer>} length = failing.map("length", s {@code ->} s.length());
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/map-2.png" height="90" width="296"/>
*
* @param <R> return type of function <code>func</code>
* @param desc description of a mapping function, it will show up in a trace
* @param func function to be applied to successful result of this task.
* @return a new task which will apply given function on result of successful completion of this task
*/
default <R> Task<R> map(final String desc, final Function1<? super T, ? extends R> func) {
ArgumentUtil.requireNotNull(func, "function");
return apply(desc, new PromiseTransformer<T, R>(func));
}
/**
* Equivalent to {@code map("map", func)}.
* @see #map(String, Function1)
*/
default <R> Task<R> map(final Function1<? super T, ? extends R> func) {
return map("map: " + _taskDescriptor.getDescription(func.getClass().getName()), func);
}
/**
* Creates a new task by applying a function to the successful result of this task and
* returns the result of a function as the new task.
* Returned task will complete with value calculated by a task returned by the function.
* <blockquote><pre>
* Task{@code <URI>} url = Task.value("uri", URI.create("http://linkedin.com"));
*
* // this task will complete with contents of a LinkedIn homepage
* // assuming fetch(u) fetches contents given by a URI
* Task{@code <String>} homepage = url.flatMap("fetch", u {@code ->} fetch(u));
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/flatMap-1.png" height="90" width="462"/>
* <p>
*
* If this task is completed with an exception then the new task will also contain
* that exception.
* <blockquote><pre>
* Task{@code <URI>} url = Task.callable("uri", () {@code ->} URI.create("not a URI"));
*
* // this task will fail with java.lang.IllegalArgumentException
* Task{@code <String>} homepage = url.flatMap("fetch", u {@code ->} fetch(u));
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/flatMap-2.png" height="90" width="296"/>
* @param <R> return type of function <code>func</code>
* @param desc description of a mapping function, it will show up in a trace
* @param func function to be applied to successful result of this task which returns new task
* to be executed
* @return a new task which will apply given function on result of successful completion of this task
* to get instance of a task which will be executed next
*/
default <R> Task<R> flatMap(final String desc, final Function1<? super T, Task<R>> func) {
ArgumentUtil.requireNotNull(func, "function");
final Function1<? super T, Task<R>> flatMapFunc = x -> {
Task<R> t = func.apply(x);
if (t == null) {
throw new RuntimeException(desc + " returned null");
} else {
return t;
}
};
final Task<Task<R>> nested = map(desc, flatMapFunc);
nested.getShallowTraceBuilder().setSystemHidden(true);
return flatten(desc, nested);
}
/**
* Equivalent to {@code flatMap("flatMap", func)}.
* @see #flatMap(String, Function1)
*/
default <R> Task<R> flatMap(final Function1<? super T, Task<R>> func) {
return flatMap("flatMap: " + _taskDescriptor.getDescription(func.getClass().getName()), func);
}
/**
* Creates a new task that will run another task as a side effect once the primary task
* completes successfully. The properties of side effect task are:
* <ul>
* <li>The side effect task will not be run if the primary task has not run e.g. due to
* failure or cancellation.</li>
* <li>The side effect does not affect returned task. It means that
* failure of side effect task is not propagated to returned task.</li>
* <li>The returned task is marked done once this task completes, even if
* the side effect has not been run yet.</li>
* </ul>
* The side effect task is useful in situations where operation (side effect) should continue to run
* in the background but it's execution is not required for the main computation. An example might
* be updating cache once data has been retrieved from the main source.
* <blockquote><pre>
* Task{@code <Long>} id = Task.value("id", 1223L);
*
* // this task will be completed as soon as user name is fetched
* // by fetch() method and will not fail even if updateMemcache() fails
* Task{@code <String>} userName = id.flatMap("fetch", u {@code ->} fetch(u))
* .withSideEffect("update memcache", u {@code ->} updateMemcache(u));
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/withSideEffect-1.png" height="120" width="868"/>
*
* @param desc description of a side effect, it will show up in a trace
* @param func function to be applied on result of successful completion of this task
* to get side effect task
* @return a new task that will run side effect task specified by given function upon successful
* completion of this task
*/
default Task<T> withSideEffect(final String desc, final Function1<? super T, Task<?>> func) {
ArgumentUtil.requireNotNull(func, "function");
final Task<T> that = this;
Task<T> withSideEffectTask = async("withSideEffect", context -> {
final Task<T> sideEffectWrapper = async(desc, ctx -> {
SettablePromise<T> promise = Promises.settable();
if (!that.isFailed()) {
Task<?> sideEffect = func.apply(that.get());
if (sideEffect == null) {
throw new RuntimeException(desc + " returned null");
} else {
ctx.runSideEffect(sideEffect);
}
}
Promises.propagateResult(that, promise);
return promise;
});
context.after(that).run(sideEffectWrapper);
context.run(that);
return sideEffectWrapper;
});
withSideEffectTask.getShallowTraceBuilder().setTaskType(TaskType.WITH_SIDE_EFFECT.getName());
return withSideEffectTask;
}
/**
* Equivalent to {@code withSideEffect("sideEffect", func)}.
* @see #withSideEffect(String, Function1)
*/
default Task<T> withSideEffect(final Function1<? super T, Task<?>> func) {
return withSideEffect("sideEffect: " + _taskDescriptor.getDescription(func.getClass().getName()), func);
}
/**
* Creates a new task that can be safely shared within a plan or between multiple
* plans. Cancellation of returned task will not cause cancellation of the original task.
* <p>
* Sharing tasks within a plan or among different plans is generally not safe because task can
* be cancelled if it's parent has been resolved. Imagine situation where <code>fetch</code>
* task that fetches data from a remote server is shared among few plans. If one of those
* plans times out then all started tasks that belong to it will be automatically cancelled.
* This means that <code>fetch</code> may also be cancelled and this can affect other plans that
* are still running. Similar situation can happen even within one plan if task is used multiple
* times.
* <p>
* In example below <code>google</code> task has timeout 10ms what causes entire plan to fail and as a consequence
* all tasks that belong to it that have been started - in this case <code>bing</code> task. This may
* be problematic if <code>bing</code> task is used somewhere else.
* <blockquote><pre>
* final Task{@code <Response>} google = HttpClient.get("http://google.com").task();
* final Task{@code <Response>} bing = HttpClient.get("http://bing.com").task();
*
* // this task will fail because google task will timeout after 10ms
* // as a consequence bing task will be cancelled
* final Task<?> both = Task.par(google.withTimeout(10, TimeUnit.MILLISECONDS), bing);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/shareable-1.png" height="250" width="608"/>
* <p>
* <code>shareable</code> method solves above problem. Task returned by <code>shareable()</code> can be
* can be cancelled without affecting original task.
*<p>
* <blockquote><pre>
* final Task{@code <Response>} google = HttpClient.get("http://google.com").task();
* final Task{@code <Response>} bing = HttpClient.get("http://bing.com").task();
*
* // this task will fail because wrapped google task will timeout after 10ms
* // notice however that original googel and bing tasks were not cancelled
* final Task<?> both =
* Task.par(google.shareable().withTimeout(10, TimeUnit.MILLISECONDS), bing.shareable());
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/shareable-2.png" height="290" width="814"/>
*
* @return new task that can be safely shared within a plan or between multiple
* plans. Cancellation of returned task will not cause cancellation of the original task.
*/
default Task<T> shareable() {
final Task<T> that = this;
Task<T> shareableTask = async("shareable", context -> {
final SettablePromise<T> result = Promises.settable();
context.runSideEffect(that);
Promises.propagateResult(that, result);
return result;
});
shareableTask.getShallowTraceBuilder().setTaskType(TaskType.SHAREABLE.getName());
return shareableTask;
}
/**
* Creates a new task which applies a consumer to the result of this task
* and completes with a result of this task. It is used
* in situations where consumer needs to be called after successful
* completion of this task.
* <blockquote><pre>
* Task{@code <String>} hello = Task.value("greeting", "Hello World");
*
* // this task will print "Hello World"
* Task{@code <String>} sayHello = hello.andThen("say", System.out::println);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/andThen-1.png" height="90" width="296"/>
* <p>
* If this task fails then consumer will not be called and failure
* will be propagated to task returned by this method.
* <blockquote><pre>
* Task{@code <String>} failing = Task.callable("greeting", () {@code ->} {
* return "Hello World".substring(100);
* });
*
* // this task will fail with java.lang.StringIndexOutOfBoundsException
* Task{@code <String>} sayHello = failing.andThen("say", System.out::println);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/andThen-2.png" height="90" width="296"/>
*
* @param desc description of a consumer, it will show up in a trace
* @param consumer consumer of a value returned by this task
* @return a new task which will complete with result of this task
*/
default Task<T> andThen(final String desc, final Consumer1<? super T> consumer) {
ArgumentUtil.requireNotNull(consumer, "consumer");
return apply(desc, new PromiseTransformer<T, T>(t -> {
consumer.accept(t);
return t;
} ));
}
/**
* Equivalent to {@code andThen("andThen", consumer)}.
* @see #andThen(String, Consumer1)
*/
default Task<T> andThen(final Consumer1<? super T> consumer) {
return andThen("andThen: " + _taskDescriptor.getDescription(consumer.getClass().getName()), consumer);
}
/**
* Creates a new task which runs given task after
* completion of this task and completes with a result of
* that task. Task passed in as a parameter will run even if
* this task fails. Notice that task passed in as a parameter
* does not depend on an actual result of this task.
* <blockquote><pre>
* // task that processes payment
* Task{@code <PaymentStatus>} processPayment = processPayment(...);
*
* // task that ships product
* Task{@code <ShipmentInfo>} shipProduct = shipProduct(...);
*
* Task{@code <ShipmentInfo>} shipAfterPayment =
* processPayment.andThen("shipProductAterPayment", shipProduct);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/andThen-3.png" height="90" width="462"/>
*
* @param <R> return type of the <code>task</code>
* @param desc description of a task, it will show up in a trace
* @param task task which will be executed after completion of this task
* @return a new task which will run given task after completion of this task
*/
default <R> Task<R> andThen(final String desc, final Task<R> task) {
ArgumentUtil.requireNotNull(task, "task");
final Task<T> that = this;
return async(desc, context -> {
final SettablePromise<R> result = Promises.settable();
context.after(that).run(task);
Promises.propagateResult(task, result);
context.run(that);
return result;
});
}
/**
* Equivalent to {@code andThen("andThen", task)}.
* @see #andThen(String, Task)
*/
default <R> Task<R> andThen(final Task<R> task) {
return andThen("andThen: " + task.getName(), task);
}
/**
* Creates a new task that will handle failure of this task.
* Early completion due to cancellation is not considered to be a failure.
* If this task completes successfully, then recovery function is not called.
* <blockquote><pre>
*
* // this method return task which asynchronously retrieves Person by id
* Task{@code <Person>} fetchPerson(Long id) {
* (...)
* }
*
* // this task will fetch Person object and transform it into {@code "<first name> <last name>"}
* // if fetching Person failed then form {@code "Member <id>"} will be return
* Task{@code <String>} userName = fetchPerson(id)
* .map("toSignature", p {@code ->} p.getFirstName() + " " + p.getLastName())
* .recover(e {@code ->} "Member " + id);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/recover-1.png" height="90" width="462"/>
* <p>
* Note that task cancellation is not considered to be a failure.
* If this task has been cancelled then task returned by this method will also
* be cancelled and recovery function will not be applied.
*
* @param desc description of a recovery function, it will show up in a trace
* @param func recovery function which can complete task with a value depending on
* failure of this task
* @return a new task which can recover from failure of this task
*/
default Task<T> recover(final String desc, final Function1<Throwable, T> func) {
ArgumentUtil.requireNotNull(func, "function");
return apply(desc, (src, dst) -> {
if (src.isFailed()) {
if (!(Exceptions.isCancellation(src.getError()))) {
try {
dst.done(func.apply(src.getError()));
} catch (Throwable t) {
dst.fail(t);
}
} else {
dst.fail(src.getError());
}
} else {
dst.done(src.get());
}
} );
}
/**
* Equivalent to {@code recover("recover", func)}.
* @see #recover(String, Function1)
*/
default Task<T> recover(final Function1<Throwable, T> func) {
return recover("recover: " + _taskDescriptor.getDescription(func.getClass().getName()), func);
}
/**
* Creates a new task which applies a consumer to the exception this
* task may fail with. It is used in situations where consumer needs
* to be called after failure of this task. Result of task returned by
* this method will be exactly the same as result of this task.
* <blockquote><pre>
* Task{@code <String>} failing = Task.callable("greeting", () {@code ->} {
* return "Hello World".substring(100);
* });
*
* // this task will print out java.lang.StringIndexOutOfBoundsException
* // and complete with that exception as a reason for failure
* Task{@code <String>} sayHello = failing.onFailure("printFailure", System.out::println);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/onFailure-1.png" height="90" width="296"/>
* <p>
* If this task completes successfully then consumer will not be called.
* <blockquote><pre>
* Task{@code <String>} hello = Task.value("greeting", "Hello World");
*
* // this task will return "Hello World"
* Task{@code <String>} sayHello = hello.onFailure(System.out::println);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/onFailure-2.png" height="90" width="296"/>
* <p>
* Exceptions thrown by a consumer will be ignored.
* <p>
* Note that task cancellation is not considered to be a failure.
* If this task has been cancelled then task returned by this method will also
* be cancelled and consumer will not be called.
*
* @param desc description of a consumer, it will show up in a trace
* @param consumer consumer of an exception this task failed with
* @return a new task which will complete with result of this task
*/
default Task<T> onFailure(final String desc, final Consumer1<Throwable> consumer) {
ArgumentUtil.requireNotNull(consumer, "consumer");
return apply(desc, (src, dst) -> {
if (src.isFailed()) {
if (!(Exceptions.isCancellation(src.getError()))) {
try {
consumer.accept(src.getError());
} catch (Exception e) {
//exceptions thrown by consumer are logged and ignored
LOGGER.error("Exception thrown by onFailure consumer: ", e);
} finally {
dst.fail(src.getError());
}
} else {
dst.fail(src.getError());
}
} else {
dst.done(src.get());
}
} );
}
/**
* Equivalent to {@code onFailure("onFailure", consumer)}.
* @see #onFailure(String, Consumer1)
*/
default Task<T> onFailure(final Consumer1<Throwable> consumer) {
return onFailure("onFailure: " + _taskDescriptor.getDescription(consumer.getClass().getName()), consumer);
}
/**
* This method transforms {@code Task<T>} into {@code Task<Try<T>>}.
* It allows explicit handling of failures by returning potential exceptions as a result of
* task execution. Task returned by this method will always complete successfully
* unless it has been cancelled.
* If this task completes successfully then return task will be
* completed with result value wrapped with {@link Success}.
* <blockquote><pre>
* Task{@code <String>} hello = Task.value("greeting", "Hello World");
*
* // this task will complete with Success("Hello World")
* Task{@code <Try<String>>} helloTry = hello.toTry("try");
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/toTry-1.png" height="90" width="296"/>
* <p>
* If this task is completed with an exception then the returned task will be
* completed with an exception wrapped with {@link Failure}.
* <blockquote><pre>
* Task{@code <String>} failing = Task.callable("greeting", () {@code ->} {
* return "Hello World".substring(100);
* });
*
* // this task will complete successfully with Failure(java.lang.StringIndexOutOfBoundsException)
* Task{@code <Try<String>>} failingTry = failing.toTry("try");
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/toTry-2.png" height="90" width="296"/>
* <p>
* All failures are automatically propagated and it is usually enough to use
* {@link #recover(String, Function1) recover} or {@link #recoverWith(String, Function1) recoverWith}.
* <p>
* Note that task cancellation is not considered to be a failure.
* If this task has been cancelled then task returned by this method will also
* be cancelled.
*
* @param desc description of a consumer, it will show up in a trace
* @return a new task that will complete successfully with the result of this task
* @see Try
* @see #recover(String, Function1) recover
* @see #recoverWith(String, Function1) recoverWith
* @see CancellationException
*/
default Task<Try<T>> toTry(final String desc) {
return apply(desc, (src, dst) -> {
final Try<T> tryT = Promises.toTry(src);
if (tryT.isFailed() && Exceptions.isCancellation(tryT.getError())) {
dst.fail(src.getError());
} else {
dst.done(Promises.toTry(src));
}
} );
}
/**
* Equivalent to {@code toTry("toTry")}.
* @see #toTry(String)
*/
default Task<Try<T>> toTry() {
return toTry("toTry");
}
/**
* Creates a new task that applies a transformation to the result of this
* task. This method allows handling both successful completion and failure
* at the same time.
* <blockquote><pre>
* Task{@code <Integer>} num = ...
*
* // this task will complete with either complete successfully
* // with String representation of num or fail with MyLibException
* Task{@code <String>} text = num.transform("toString", t {@code ->} {
* if (t.isFailed()) {
* return Failure.of(new MyLibException(t.getError()));
* } else {
* return Success.of(String.valueOf(t.get()));
* }
* });
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/transform-1.png" height="90" width="296"/>
* <p>
* Note that task cancellation is not considered to be a failure.
* If this task has been cancelled then task returned by this method will also
* be cancelled and transformation will not be applied.
*
* @param <R> type parameter of function <code>func</code> return <code>Try</code>
* @param desc description of a consumer, it will show up in a trace
* @param func a transformation to be applied to the result of this task
* @return a new task that will apply a transformation to the result of this task
* @see Try
*/
default <R> Task<R> transform(final String desc, final Function1<Try<T>, Try<R>> func) {
ArgumentUtil.requireNotNull(func, "function");
return apply(desc, (src, dst) -> {
final Try<T> tryT = Promises.toTry(src);
if (tryT.isFailed() && Exceptions.isCancellation(tryT.getError())) {
dst.fail(src.getError());
} else {
try {
final Try<R> tryR = func.apply(tryT);
if (tryR.isFailed()) {
dst.fail(tryR.getError());
} else {
dst.done(tryR.get());
}
} catch (Exception e) {
dst.fail(e);
}
}
} );
}
/**
* Equivalent to {@code transform("transform", func)}.
* @see #transform(String, Function1)
*/
default <R> Task<R> transform(final Function1<Try<T>, Try<R>> func) {
return transform("transform: " + _taskDescriptor.getDescription(func.getClass().getName()), func);
}
/**
* Creates a new task that will handle failure of this task.
* Early completion due to cancellation is not considered to be a failure.
* If this task completes successfully, then recovery function is not called.
* <blockquote><pre>
*
* // this method return task which asynchronously retrieves Person by id from cache
* Task{@code <Person>} fetchFromCache(Long id) {
* (...)
* }
*
* // this method return task which asynchronously retrieves Person by id from DB
* Task{@code <Person>} fetchFromDB(Long id) {
* (...)
* }
*
* // this task will try to fetch Person from cache and
* // if it fails for any reason it will attempt to fetch from DB
* Task{@code <Person>} user = fetchFromCache(id).recoverWith(e {@code ->} fetchFromDB(id));
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/recoverWith-1.png" height="90" width="462"/>
* <p>
* If recovery task fails then returned task is completed with that failure.
* <p>
* Note that task cancellation is not considered to be a failure.
* If this task has been cancelled then task returned by this method will also
* be cancelled and recovery function will not be applied.
*
* @param desc description of a recovery function, it will show up in a trace
* @param func recovery function provides task which will be used to recover from
* failure of this task
* @return a new task which can recover from failure of this task
*/
default Task<T> recoverWith(final String desc, final Function1<Throwable, Task<T>> func) {
ArgumentUtil.requireNotNull(func, "function");
final Task<T> that = this;
Task<T> recoverWithTask = async(desc, context -> {
final SettablePromise<T> result = Promises.settable();
final Task<T> recovery = async("recovery", ctx -> {
final SettablePromise<T> recoveryResult = Promises.settable();
if (that.isFailed()) {
if (!(Exceptions.isCancellation(that.getError()))) {
try {
Task<T> r = func.apply(that.getError());
if (r == null) {
throw new RuntimeException(desc + " returned null");
}
Promises.propagateResult(r, recoveryResult);
ctx.run(r);
} catch (Throwable t) {
recoveryResult.fail(t);
}
} else {
recoveryResult.fail(that.getError());
}
} else {
recoveryResult.done(that.get());
}
return recoveryResult;
});
recovery.getShallowTraceBuilder().setSystemHidden(true);
recovery.getShallowTraceBuilder().setTaskType(TaskType.RECOVER.getName());
Promises.propagateResult(recovery, result);
context.after(that).run(recovery);
context.run(that);
return result;
});
recoverWithTask.getShallowTraceBuilder().setTaskType(TaskType.WITH_RECOVER.getName());
return recoverWithTask;
}
/**
* Equivalent to {@code recoverWith("recoverWith", func)}.
* @see #recoverWith(String, Function1)
*/
default Task<T> recoverWith(final Function1<Throwable, Task<T>> func) {
return recoverWith("recoverWith: " + _taskDescriptor.getDescription(func.getClass().getName()), func);
}
/**
* Equivalent to {@code withTimeout(null, time, unit)}.
* @see #withTimeout(String, long, TimeUnit)
*/
default Task<T> withTimeout(final long time, final TimeUnit unit) {
return withTimeout(null, time, unit);
}
/**
* Creates a new task that has a timeout associated with it. If this task finishes
* before the timeout occurs then returned task will be completed with the value of this task.
* If this task does not complete in the given time then returned task will
* fail with a {@link TimeoutException} as a reason of failure and this task will be cancelled.
* <blockquote><pre>
* final Task<Response> google = HttpClient.get("http://google.com").task()
* .withTimeout(10, TimeUnit.MILLISECONDS);
* </pre></blockquote>
* <img src="https://raw.githubusercontent.com/linkedin/parseq/master/src/com/linkedin/parseq/doc-files/withTimeout-1.png" height="150" width="318"/>
*
* @param desc description of a timeout. There is no need to put timeout value here because it will be automatically
* included. Full description of a timeout will be: {@code "withTimeout " + time + " " + TimeUnitHelper.toString(unit) +
* (desc != null ? " " + desc : "")}. It is a good idea to put information that will help understand why the timeout
* was specified e.g. if timeout was specified by a configuration, the configuration parameter name would be a useful
* information
* @param time the time to wait before timing out
* @param unit the units for the time
* @return the new task with a timeout
*/
default Task<T> withTimeout(final String desc, final long time, final TimeUnit unit) {
final Task<T> that = this;
final String taskName = "withTimeout " + time + TimeUnitHelper.toString(unit) +
(desc != null ? " " + desc : "");
Task<T> withTimeout = async(taskName, ctx -> {
final AtomicBoolean committed = new AtomicBoolean();
final SettablePromise<T> result = Promises.settable();
final Task<?> timeoutTask = Task.action("timeout", () -> {
if (committed.compareAndSet(false, true)) {
result.fail(Exceptions.timeoutException(taskName));
}
} );
//timeout tasks should run as early as possible
timeoutTask.setPriority(Priority.MAX_PRIORITY);
timeoutTask.getShallowTraceBuilder().setTaskType(TaskType.TIMEOUT.getName());
ctx.createTimer(time, unit, timeoutTask);
that.addListener(p -> {
if (committed.compareAndSet(false, true)) {
Promises.propagateResult(that, result);
}
} );
//we want to schedule this task as soon as possible
//because timeout timer has started ticking
that.setPriority(Priority.MAX_PRIORITY);
ctx.run(that);
return result;
});
withTimeout.setPriority(getPriority());
withTimeout.getShallowTraceBuilder().setTaskType(TaskType.WITH_TIMEOUT.getName());
return withTimeout;
}
/**
* Converts {@code Task<Task<R>>} into {@code Task<R>}.
* @param <R> return type of nested <code>task</code>
* @param desc description that will show up in a trace
* @param task task to be flattened
* @return flattened task
*/
public static <R> Task<R> flatten(final String desc, final Task<Task<R>> task) {
ArgumentUtil.requireNotNull(task, "task");
Task<R> flattenTask = async(desc, context -> {
final SettablePromise<R> result = Promises.settable();
context.after(task).run(() -> {
try {
if (!task.isFailed()) {
Task<R> t = task.get();
if (t == null) {
throw new RuntimeException(desc + " returned null");
} else {
Promises.propagateResult(t, result);
return t;
}
} else {
result.fail(task.getError());
}
} catch (Throwable t) {
result.fail(t);
}
return null;
} );
context.run(task);
return result;
});
flattenTask.getShallowTraceBuilder().setTaskType(TaskType.FLATTEN.getName());
return flattenTask;
}
/**
* Equivalent to {@code flatten("flatten", task)}.
* @see #flatten(String, Task)
*/
public static <R> Task<R> flatten(final Task<Task<R>> task) {
return flatten("flatten", task);
}
//------------------- static factory methods -------------------
/**
* Creates a new task that have a value of type {@code Void}. Because the
* returned task returns no value, it is typically used to produce side effects.
* It is not appropriate for long running or blocking actions. If action is
* long running or blocking use {@link #blocking(String, Callable, Executor) blocking} method.
*
* <blockquote><pre>
* // this task will print "Hello" on standard output
* Task{@code <Void>} task = Task.action("greeting", () {@code ->} System.out.println("Hello"));
* </pre></blockquote>
*
* Returned task will fail if {@code Action} passed in as a parameter throws
* an exception.
* <blockquote><pre>
* // this task will fail with java.lang.ArithmeticException
* Task{@code <Void>} task = Task.action("division", () {@code ->} System.out.println(2 / 0));
* </pre></blockquote>
* <p>
* @param desc a description the action, it will show up in a trace
* @param action the action that will be executed when the task is run
* @return the new task that will execute the action
*/
public static Task<Void> action(final String desc, final Action action) {
ArgumentUtil.requireNotNull(action, "action");
return async(desc, () -> {
action.run();
return Promises.VOID;
});
}
/**
* Equivalent to {@code action("action", action)}.
* @see #action(String, Action)
*/
public static Task<Void> action(final Action action) {
return action("action: " + _taskDescriptor.getDescription(action.getClass().getName()), action);
}
/**
* Creates a new task that will be resolved with given value when it is
* executed. Note that this task is not initially completed.
*
* @param <T> type of the value
* @param desc a description of the value, it will show up in a trace
* @param value a value the task will be resolved with
* @return new task that will be resolved with given value when it is
* executed
*/
public static <T> Task<T> value(final String desc, final T value) {
return FusionTask.create(desc, (src, dst) -> {
dst.done(value);
} );
}
/**
* Equivalent to {@code value("value", value)}.
* @see #value(String, Object)
*/
public static <T> Task<T> value(final T value) {
return value("value", value);
}
/**
* Creates a new task that will be fail with given exception when it is
* executed. Note that this task is not initially completed.
*
* @param <T> type parameter of the returned task
* @param desc a description of the failure, it will show up in a trace
* @param failure a failure the task will fail with
* @return new task that will fail with given failure when it is
* executed
*/
public static <T> Task<T> failure(final String desc, final Throwable failure) {
ArgumentUtil.requireNotNull(failure, "failure");
return FusionTask.create(desc, (src, dst) -> {
dst.fail(failure);
} );
}
/**
* Equivalent to {@code failure("failure", failure)}.
* @see #failure(String, Throwable)
*/
public static <T> Task<T> failure(final Throwable failure) {
return failure("failure", failure);
}
/**
* Creates a new task that's value will be set to the value returned
* from the supplied callable. This task is useful when doing basic
* computation that does not require asynchrony. It is not appropriate for
* long running or blocking callables. If callable is long running or blocking
* use {@link #blocking(String, Callable, Executor) blocking} method.
*
* <blockquote><pre>
* // this task will complete with {@code String} representing current time
* Task{@code <String>} task = Task.callable("current time", () {@code ->} new Date().toString());
* </pre></blockquote>
*
* Returned task will fail if callable passed in as a parameter throws
* an exception.
* <blockquote><pre>
* // this task will fail with java.lang.ArithmeticException
* Task{@code <Integer>} task = Task.callable("division", () {@code ->} 2 / 0);
* </pre></blockquote>
* <p>
* @param <T> the type of the return value for this task
* @param name a name that describes the task, it will show up in a trace
* @param callable the callable to execute when this task is run
* @return the new task that will invoke the callable and complete with it's result
*/
public static <T> Task<T> callable(final String name, final Callable<? extends T> callable) {
ArgumentUtil.requireNotNull(callable, "callable");
return FusionTask.create(name, (src, dst) -> {
try {
dst.done(callable.call());
} catch (Throwable t) {
dst.fail(t);
}
} );
}
/**
* Equivalent to {@code callable("callable", callable)}.
* @see #callable(String, Callable)
*/
public static <T> Task<T> callable(final Callable<? extends T> callable) {
return callable("callable: " + _taskDescriptor.getDescription(callable.getClass().getName()), callable);
}
/**
* Creates a new task from a callable that returns a {@link Promise}.
* This method is mainly used to integrate ParSeq with 3rd party
* asynchronous libraries. It should not be used in order to compose
* or manipulate existing tasks. The following example shows how to integrate
* <a href="https://github.com/AsyncHttpClient">AsyncHttpClient</a> with ParSeq.
*
* <blockquote><pre>
* // Creates a task that asynchronouslyexecutes given HTTP request
* // and will complete with HTTP response. It uses asyncHttpRequest()
* // method as a lambda of shape: ThrowableCallable{@code <Promise<Response>>}.
* Task{@code <Response>} httpTask(final Request request) {
* return Task.async(() {@code ->} asyncHttpRequest(request), false);
* }
*
* // This method uses HTTP_CLIENT to make asynchronous
* // request and returns a Promise that will be resolved once
* // the HTTP request is completed.
* Promise{@code <Response>} asyncHttpRequest(final Request request) {
*
* // Create a settable promise. We'll use this to signal completion of this
* // task once the response is received from the HTTP client.
* final SettablePromise{@code <Response>} promise = Promises.settable();
*
* // Send the request and register a callback with the client that will
* // set the response on our promise.
* HTTP_CLIENT.prepareRequest(request).execute(new AsyncCompletionHandler{@code <Response>}() {
*
* {@code @Override}
* public Response onCompleted(final Response response) throws Exception {
* // At this point the HTTP client has given us the HTTP response
* // asynchronously. We set the response value on our promise to indicate
* // that the task is complete.
* promise.done(response);
* return response;
* }
*
* {@code @Override}
* public void onThrowable(final Throwable t) {
* // If there was an error then we should set it on the promise.
* promise.fail(t);
* }
* });
*
* // Return the promise. It may or may not be
* // resolved by the time we return this promise.
* return promise;
* }
* </pre></blockquote>
*
* This method is not appropriate for long running or blocking callables.
* If callable is long running or blocking use
* {@link #blocking(String, Callable, Executor) blocking} method.
* <p>
*
* @param <T> the type of the return value for this task
* @param name a name that describes the task, it will show up in a trace
* @param callable a callable to execute when this task is run, it must return
* a {@code Promise<T>}
* @return a new task that will invoke the callable and complete with result
* returned by a {@code Promise} returned by it
* @see Promise
*/
public static <T> Task<T> async(final String name, final Callable<Promise<? extends T>> callable) {
ArgumentUtil.requireNotNull(callable, "callable");
return async(name, context -> {
try {
return callable.call();
} catch (Throwable e) {
return Promises.error(e);
}
});
}
/**
* Equivalent to {@code async("async", callable)}.
* @see #async(String, Callable)
*/
public static <T> Task<T> async(final Callable<Promise<? extends T>> callable) {
return async("async: " + _taskDescriptor.getDescription(callable.getClass().getName()), callable);
}
/**
* Creates a new task from a callable that returns a {@link Promise}.
* This method is mainly used to build functionality that has not been provided
* by ParSeq API. It gives access to {@link Context} which allows scheduling
* tasks in current plan. This method almost never should be necessary. If you
* feel the need to use this method, please contact ParSeq team to help us
* improve our API.
*
* @param <T> the type of the return value for this task
* @param name a name that describes the task, it will show up in a trace
* @param func a function to execute when this task is run, it must return
* a {@code Promise<T>}
* @return a new task that will invoke the function and complete with result
* returned by a {@code Promise} returned by it
* @see Context
* @see Promise
*/
public static <T> Task<T> async(final String name, final Function1<Context, Promise<? extends T>> func) {
ArgumentUtil.requireNotNull(func, "function");
Task<T> task = new BaseTask<T>(name) {
@Override
protected Promise<? extends T> run(Context context) throws Throwable {
return func.apply(context);
}
};
return task;
}
/**
* Equivalent to {@code async("async", func)}.
* @see #async(String, Function1)
*/
public static <T> Task<T> async(final Function1<Context, Promise<? extends T>> func) {
return async("async: " + _taskDescriptor.getDescription(func.getClass().getName()), func);
}
/**
* This method provides a way to create an asynchronous task from
* a blocking or long running callables like JDBC requests.
* Unlike with tasks created with all other methods a callable passed
* as a parameter is not executed on ParSeq's thread but instead it is
* executed on specified {@link Executor}. It means that callable
* does not get any special memory consistency guarantees and should not
* attempt to use shared state.
* <p>
* In order to avoid creating tasks that never complete the Executor passed in as a
* parameter <b>must</b> signal execution rejection by throwing an exception.
* <p>
* In order to prevent blocking ParSeq threads the Executor passed in as a
* parameter <b>must not</b> use {@link ThreadPoolExecutor.CallerRunsPolicy}
* as a rejection policy.
*
* @param <T> the type of the return value for this task
* @param name a name that describes the task, it will show up in a trace
* @param callable a callable that will provide result
* @param executor {@code Executor} that will be used to run the callable
* @return a new task that will submit the callable to given executor and complete
* with result returned by that callable
*/
public static <T> Task<T> blocking(final String name, final Callable<? extends T> callable, final Executor executor) {
ArgumentUtil.requireNotNull(callable, "callable");
ArgumentUtil.requireNotNull(callable, "executor");
Task<T> blockingTask = async(name, () -> {
final SettablePromise<T> promise = Promises.settable();
executor.execute(() -> {
try {
promise.done(callable.call());
} catch (Throwable t) {
promise.fail(t);
}
} );
return promise;
});
blockingTask.getShallowTraceBuilder().setTaskType(TaskType.BLOCKING.getName());
return blockingTask;
}
/**
* Equivalent to {@code blocking("blocking", callable, executor)}.
* @see #blocking(String, Callable, Executor)
*/
public static <T> Task<T> blocking(final Callable<? extends T> callable, final Executor executor) {
return blocking("blocking: " + _taskDescriptor.getDescription(callable.getClass().getName()), callable, executor);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2> Tuple2Task<T1, T2> par(final Task<T1> task1, final Task<T2> task2) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
return new Par2Task<T1, T2>("par2", task1, task2);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2, T3> Tuple3Task<T1, T2, T3> par(final Task<T1> task1, final Task<T2> task2,
final Task<T3> task3) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
ArgumentUtil.requireNotNull(task3, "task3");
return new Par3Task<T1, T2, T3>("par3", task1, task2, task3);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2, T3, T4> Tuple4Task<T1, T2, T3, T4> par(final Task<T1> task1, final Task<T2> task2,
final Task<T3> task3, final Task<T4> task4) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
ArgumentUtil.requireNotNull(task3, "task3");
ArgumentUtil.requireNotNull(task4, "task4");
return new Par4Task<T1, T2, T3, T4>("par4", task1, task2, task3, task4);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2, T3, T4, T5> Tuple5Task<T1, T2, T3, T4, T5> par(final Task<T1> task1, final Task<T2> task2,
final Task<T3> task3, final Task<T4> task4, final Task<T5> task5) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
ArgumentUtil.requireNotNull(task3, "task3");
ArgumentUtil.requireNotNull(task4, "task4");
ArgumentUtil.requireNotNull(task5, "task5");
return new Par5Task<T1, T2, T3, T4, T5>("par5", task1, task2, task3, task4, task5);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2, T3, T4, T5, T6> Tuple6Task<T1, T2, T3, T4, T5, T6> par(final Task<T1> task1,
final Task<T2> task2, final Task<T3> task3, final Task<T4> task4, final Task<T5> task5, final Task<T6> task6) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
ArgumentUtil.requireNotNull(task3, "task3");
ArgumentUtil.requireNotNull(task4, "task4");
ArgumentUtil.requireNotNull(task5, "task5");
ArgumentUtil.requireNotNull(task6, "task6");
return new Par6Task<T1, T2, T3, T4, T5, T6>("par6", task1, task2, task3, task4, task5, task6);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2, T3, T4, T5, T6, T7> Tuple7Task<T1, T2, T3, T4, T5, T6, T7> par(final Task<T1> task1,
final Task<T2> task2, final Task<T3> task3, final Task<T4> task4, final Task<T5> task5, final Task<T6> task6,
final Task<T7> task7) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
ArgumentUtil.requireNotNull(task3, "task3");
ArgumentUtil.requireNotNull(task4, "task4");
ArgumentUtil.requireNotNull(task5, "task5");
ArgumentUtil.requireNotNull(task6, "task6");
ArgumentUtil.requireNotNull(task7, "task7");
return new Par7Task<T1, T2, T3, T4, T5, T6, T7>("par7", task1, task2, task3, task4, task5, task6, task7);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2, T3, T4, T5, T6, T7, T8> Tuple8Task<T1, T2, T3, T4, T5, T6, T7, T8> par(final Task<T1> task1,
final Task<T2> task2, final Task<T3> task3, final Task<T4> task4, final Task<T5> task5, final Task<T6> task6,
final Task<T7> task7, final Task<T8> task8) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
ArgumentUtil.requireNotNull(task3, "task3");
ArgumentUtil.requireNotNull(task4, "task4");
ArgumentUtil.requireNotNull(task5, "task5");
ArgumentUtil.requireNotNull(task6, "task6");
ArgumentUtil.requireNotNull(task7, "task7");
ArgumentUtil.requireNotNull(task8, "task8");
return new Par8Task<T1, T2, T3, T4, T5, T6, T7, T8>("par8", task1, task2, task3, task4, task5, task6, task7, task8);
}
/**
* Creates a new task that will run given tasks in parallel. Returned task
* will be resolved with results of all tasks as soon as all of them has
* been completed successfully.
*
* <blockquote><pre>
* // this task will asynchronously fetch user and company in parallel
* // and create signature in a form {@code "<first name> <last name> working for <company>"}
* Task{@code <String>} signature =
* Task.par(fetchUser(userId), fetchCompany(companyId))
* .map((user, company) {@code ->}
* user.getFirstName() + user.getLastName() + " working for " + company.getName());
* </pre></blockquote>
*
* If any of tasks passed in as a parameters fails then returned task will also fail immediately.
* In this case returned task will be resolved with error from the first of failing tasks and other
* tasks will be cancelled (if possible).
* <p>
* @return task that will run given tasks in parallel
*/
public static <T1, T2, T3, T4, T5, T6, T7, T8, T9> Tuple9Task<T1, T2, T3, T4, T5, T6, T7, T8, T9> par(
final Task<T1> task1, final Task<T2> task2, final Task<T3> task3, final Task<T4> task4, final Task<T5> task5,
final Task<T6> task6, final Task<T7> task7, final Task<T8> task8, final Task<T9> task9) {
ArgumentUtil.requireNotNull(task1, "task1");
ArgumentUtil.requireNotNull(task2, "task2");
ArgumentUtil.requireNotNull(task3, "task3");
ArgumentUtil.requireNotNull(task4, "task4");
ArgumentUtil.requireNotNull(task5, "task5");
ArgumentUtil.requireNotNull(task6, "task6");
ArgumentUtil.requireNotNull(task7, "task7");
ArgumentUtil.requireNotNull(task8, "task8");
ArgumentUtil.requireNotNull(task9, "task9");
return new Par9Task<T1, T2, T3, T4, T5, T6, T7, T8, T9>("par9", task1, task2, task3, task4, task5, task6, task7,
task8, task9);
}
/**
* Creates a new task that will run each of the supplied tasks in parallel (e.g.
* tasks[0] can be run at the same time as tasks[1]).
* <p>
* When all tasks complete successfully, you can use
* {@link com.linkedin.parseq.ParTask#get()} to get a list of the results. If
* at least one task failed, then this task will also be marked as failed. Use
* {@link com.linkedin.parseq.ParTask#getTasks()} or
* {@link com.linkedin.parseq.ParTask#getSuccessful()} to get results in this
* case.
* <p>
* If the Iterable of tasks is empty, {@link com.linkedin.parseq.ParTask#get()}
* will return an empty list.
* <p>
* Note that resulting task does not fast-fail e.g. if one of the tasks fail others
* are not cancelled. This is different behavior than {@link Task#par(Task, Task)} where
* resulting task fast-fails.
*
* @param tasks the tasks to run in parallel
* @return The results of the tasks
*/
public static <T> ParTask<T> par(final Iterable<? extends Task<? extends T>> tasks) {
return tasks.iterator().hasNext()
? new ParTaskImpl<T>("par", tasks)
: new ParTaskImpl<T>("par");
}
/**
* Equivalent to {@code withRetryPolicy("operation", policy, taskSupplier)}.
* @see #withRetryPolicy(String, RetryPolicy, Callable)
*/
public static <T> Task<T> withRetryPolicy(RetryPolicy policy, Callable<Task<T>> taskSupplier) {
return withRetryPolicy("operation", policy, taskSupplier);
}
/**
* Equivalent to {@code withRetryPolicy("operation", policy, taskSupplier)}.
* @see #withRetryPolicy(String, RetryPolicy, Callable)
*/
public static <T> Task<T> withRetryPolicy(RetryPolicy policy, Function1<Integer, Task<T>> taskSupplier) {
return withRetryPolicy("operation", policy, taskSupplier);
}
/**
* Creates a new task that will run and potentially retry task returned
* by a {@code taskSupplier}. Use {@link RetryPolicyBuilder} to create desired
* retry policy.
* <p>
* NOTE: using tasks with retry can have significant performance implications. For example, HTTP request may
* failed due to server overload and retrying request may prevent server from recovering. In this example
* a better approach is the opposite: decrease number of requests to the server unit it is fully recovered.
* Please make sure you have considered why the first task failed and why is it reasonable to expect retry task
* to complete successfully. It is also highly recommended to specify reasonable backoff and termination conditions.
*
* @param name A name of the task that needs to be retried.
* @param policy Retry policy that will control this task's retry behavior.
* @param taskSupplier A task generator function.
* @param <T> the type of the return value for this task
*/
public static <T> Task<T> withRetryPolicy(String name, RetryPolicy policy, Callable<Task<T>> taskSupplier) {
return withRetryPolicy(name, policy, attempt -> taskSupplier.call());
}
/**
* Creates a new task that will run and potentially retry task returned
* by a {@code taskSupplier}. Use {@link RetryPolicyBuilder} to create desired
* retry policy.
* <p>
* NOTE: using tasks with retry can have significant performance implications. For example, HTTP request may
* failed due to server overload and retrying request may prevent server from recovering. In this example
* a better approach is the opposite: decrease number of requests to the server unit it is fully recovered.
* Please make sure you have considered why the first task failed and why is it reasonable to expect retry task
* to complete successfully. It is also highly recommended to specify reasonable backoff and termination conditions.
*
* @param name A name of the task that needs to be retried.
* @param policy Retry policy that will control this task's retry behavior.
* @param taskSupplier A task generator function. It will receive a zero-based attempt number as a parameter.
* @param <T> the type of the return value for this task
* @see RetryPolicyBuilder
*/
public static <T> Task<T> withRetryPolicy(String name, RetryPolicy policy, Function1<Integer, Task<T>> taskSupplier) {
return RetriableTask.withRetryPolicy(name, policy, taskSupplier);
}
}