Remove unused Bolts code from runtime (#44779)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/44779

We forked a copy of Bolts when we open-sourced bridgeless, but it contains many features we don't require, since we only use Tasks to orchestrate the bridgeless startup path.

The only meaningful change I made is removing the fallback on stack overflow from the immediate executor, which is not something we expect to hit during startup, and would be better surfaced as a StackOverflowException.

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D58087989

fbshipit-source-id: a4908723a04bf47fdc38d91bf47df928b91456f5
This commit is contained in:
Pieter De Baets
2024-06-04 10:36:09 -07:00
committed by Facebook GitHub Bot
parent a9bcc0d46c
commit 95c721646d
13 changed files with 100 additions and 1446 deletions
@@ -3835,54 +3835,20 @@ public final class com/facebook/react/runtime/hermes/HermesInstance : com/facebo
public final class com/facebook/react/runtime/hermes/HermesInstance$Companion {
}
public class com/facebook/react/runtime/internal/bolts/CancellationToken {
public fun isCancellationRequested ()Z
public fun register (Ljava/lang/Runnable;)Lcom/facebook/react/runtime/internal/bolts/CancellationTokenRegistration;
public fun throwIfCancellationRequested ()V
public fun toString ()Ljava/lang/String;
}
public class com/facebook/react/runtime/internal/bolts/CancellationTokenSource : java/io/Closeable {
public fun <init> ()V
public fun cancel ()V
public fun cancelAfter (J)V
public fun close ()V
public fun getToken ()Lcom/facebook/react/runtime/internal/bolts/CancellationToken;
public fun isCancellationRequested ()Z
public fun toString ()Ljava/lang/String;
}
public abstract interface class com/facebook/react/runtime/internal/bolts/Continuation {
public abstract fun then (Lcom/facebook/react/runtime/internal/bolts/Task;)Ljava/lang/Object;
}
public class com/facebook/react/runtime/internal/bolts/Task : com/facebook/react/interfaces/TaskInterface {
public static final field BACKGROUND_EXECUTOR Ljava/util/concurrent/ExecutorService;
public static final field UI_THREAD_CONDITIONAL_SYNC_EXECUTOR Ljava/util/concurrent/Executor;
public static final field UI_THREAD_EXECUTOR Ljava/util/concurrent/Executor;
public static fun call (Ljava/util/concurrent/Callable;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun call (Ljava/util/concurrent/Callable;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun call (Ljava/util/concurrent/Callable;Ljava/util/concurrent/Executor;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun call (Ljava/util/concurrent/Callable;Ljava/util/concurrent/Executor;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun callInBackground (Ljava/util/concurrent/Callable;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun callInBackground (Ljava/util/concurrent/Callable;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun cancelled ()Lcom/facebook/react/runtime/internal/bolts/Task;
public fun cast ()Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWhile (Ljava/util/concurrent/Callable;Lcom/facebook/react/runtime/internal/bolts/Continuation;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWhile (Ljava/util/concurrent/Callable;Lcom/facebook/react/runtime/internal/bolts/Continuation;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWhile (Ljava/util/concurrent/Callable;Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWhile (Ljava/util/concurrent/Callable;Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWith (Lcom/facebook/react/runtime/internal/bolts/Continuation;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWith (Lcom/facebook/react/runtime/internal/bolts/Continuation;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWith (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWith (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWithTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWithTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWithTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun continueWithTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun create ()Lcom/facebook/react/runtime/internal/bolts/TaskCompletionSource;
public static fun delay (J)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun delay (JLcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun forError (Ljava/lang/Exception;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun forResult (Ljava/lang/Object;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun getError ()Ljava/lang/Exception;
@@ -3893,20 +3859,12 @@ public class com/facebook/react/runtime/internal/bolts/Task : com/facebook/react
public fun isFaulted ()Z
public fun makeVoid ()Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccess (Lcom/facebook/react/runtime/internal/bolts/Continuation;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccess (Lcom/facebook/react/runtime/internal/bolts/Continuation;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccess (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccess (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccessTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccessTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccessTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;)Lcom/facebook/react/runtime/internal/bolts/Task;
public fun onSuccessTask (Lcom/facebook/react/runtime/internal/bolts/Continuation;Ljava/util/concurrent/Executor;Lcom/facebook/react/runtime/internal/bolts/CancellationToken;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun setUnobservedExceptionHandler (Lcom/facebook/react/runtime/internal/bolts/Task$UnobservedExceptionHandler;)V
public fun waitForCompletion ()V
public fun waitForCompletion (JLjava/util/concurrent/TimeUnit;)Z
public static fun whenAll (Ljava/util/Collection;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun whenAllResult (Ljava/util/Collection;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun whenAny (Ljava/util/Collection;)Lcom/facebook/react/runtime/internal/bolts/Task;
public static fun whenAnyResult (Ljava/util/Collection;)Lcom/facebook/react/runtime/internal/bolts/Task;
}
public abstract interface class com/facebook/react/runtime/internal/bolts/Task$UnobservedExceptionHandler {
@@ -1,93 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime;
import com.facebook.react.runtime.internal.bolts.CancellationTokenSource;
import com.facebook.react.runtime.internal.bolts.Task;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* This class is a {@link Future} that holds an instance of {@link Task}. The implementation of this
* class delegates its behavior on the held task, following the {@link Future} interface defined in
* {@link "https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html"}
*
* @param <T> The type of the result of the task.
*/
class BoltsFutureTask<T> implements Future<T> {
private final Task<T> mTask;
private boolean isTaskCancelled = false;
private final CancellationTokenSource mCancellationTokenSource;
private BoltsFutureTask(Task<T> task) {
this(task, new CancellationTokenSource());
}
/**
* Creates a new instance of {@link BoltsFutureTask} for a task that handles cancellation. For
* more details about bolts cancellation refer to {@link
* "https://github.com/BoltsFramework/Bolts-Android#cancelling-tasks"}
*
* @param task {@link Task} to be held by BoltsFutureTask
* @param cancellationTokenSource {@link CancellationTokenSource} object that is used by the task
* received by parameter to handle cancellation.
*/
private BoltsFutureTask(Task<T> task, CancellationTokenSource cancellationTokenSource) {
mTask = task;
mCancellationTokenSource = cancellationTokenSource;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
try {
if (!isDone()) {
mCancellationTokenSource.cancel();
}
return true;
} finally {
isTaskCancelled = true;
}
}
@Override
public boolean isCancelled() {
return isTaskCancelled || mTask.isCancelled();
}
@Override
public boolean isDone() {
return isTaskCancelled || mTask.isCancelled() || mTask.isFaulted() || mTask.isCompleted();
}
@Override
public T get() throws ExecutionException, InterruptedException {
mTask.waitForCompletion();
return getResult(mTask);
}
@Override
public T get(long timeout, TimeUnit unit)
throws ExecutionException, InterruptedException, TimeoutException {
if (mTask.waitForCompletion(timeout, unit)) {
return getResult(mTask);
}
throw new TimeoutException();
}
private T getResult(Task<T> task) throws ExecutionException {
if (task.isFaulted()) {
throw new ExecutionException("", new Throwable());
} else if (task.isCancelled()) {
throw new CancellationException("");
}
return task.getResult();
}
}
@@ -8,7 +8,6 @@
package com.facebook.react.runtime;
import static com.facebook.infer.annotation.Assertions.assertNotNull;
import static com.facebook.infer.annotation.Assertions.nullsafeFIXME;
import static com.facebook.infer.annotation.ThreadConfined.UI;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
@@ -63,7 +62,6 @@ import com.facebook.react.interfaces.fabric.ReactSurface;
import com.facebook.react.modules.appearance.AppearanceModule;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.runtime.internal.bolts.Continuation;
import com.facebook.react.runtime.internal.bolts.Task;
import com.facebook.react.runtime.internal.bolts.TaskCompletionSource;
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder;
@@ -117,10 +115,7 @@ public class ReactHostImpl implements ReactHost {
Collections.synchronizedList(new ArrayList<>());
private final BridgelessAtomicRef<Task<ReactInstance>> mReactInstanceTaskRef =
new BridgelessAtomicRef<>(
Task.forResult(
nullsafeFIXME(
null, "forResult parameter supports null, but is not annotated as @Nullable")));
new BridgelessAtomicRef<>(Task.forResult(null));
private final BridgelessAtomicRef<BridgelessReactContext> mBridgelessReactContextRef =
new BridgelessAtomicRef<>();
@@ -912,6 +907,7 @@ public class ReactHostImpl implements ReactHost {
TAG, new ReactNoCrashSoftException(method + ": " + message));
}
/** Schedule work on a ReactInstance that is already created. */
private Task<Boolean> callWithExistingReactInstance(
final String callingMethod, final VeniceThenable<ReactInstance> continuation) {
final String method = "callWithExistingReactInstance(" + callingMethod + ")";
@@ -922,7 +918,7 @@ public class ReactHostImpl implements ReactHost {
task -> {
final ReactInstance reactInstance = task.getResult();
if (reactInstance == null) {
raiseSoftException(method, "Execute: ReactInstance null. Dropping work.");
raiseSoftException(method, "Execute: reactInstance is null. Dropping work.");
return FALSE;
}
@@ -932,23 +928,23 @@ public class ReactHostImpl implements ReactHost {
mBGExecutor);
}
/** Create a ReactInstance if it doesn't exist already, and schedule work on it. */
private Task<Void> callAfterGetOrCreateReactInstance(
final String callingMethod, final VeniceThenable<ReactInstance> runnable) {
final String method = "callAfterGetOrCreateReactInstance(" + callingMethod + ")";
return getOrCreateReactInstance()
.onSuccess(
(Continuation<ReactInstance, Void>)
task -> {
final ReactInstance reactInstance = task.getResult();
if (reactInstance == null) {
raiseSoftException(method, "Execute: ReactInstance is null");
return null;
}
task -> {
final ReactInstance reactInstance = task.getResult();
if (reactInstance == null) {
raiseSoftException(method, "Execute: reactInstance is null. Dropping work.");
return null;
}
runnable.then(reactInstance);
return null;
},
runnable.then(reactInstance);
return null;
},
mBGExecutor)
.continueWith(
task -> {
@@ -1253,7 +1249,7 @@ public class ReactHostImpl implements ReactHost {
ReactInstance unwrap(Task<ReactInstance> task, String stage);
}
private ReactInstanceTaskUnwrapper createReactInstanceUnwraper(
private ReactInstanceTaskUnwrapper createReactInstanceUnwrapper(
String tag, String method, String reason) {
return (task, stage) -> {
@@ -1321,7 +1317,7 @@ public class ReactHostImpl implements ReactHost {
raiseSoftException(method, reason);
ReactInstanceTaskUnwrapper reactInstanceTaskUnwrapper =
createReactInstanceUnwraper("Reload", method, reason);
createReactInstanceUnwrapper("Reload", method, reason);
if (mReloadTask == null) {
mReloadTask =
@@ -1497,7 +1493,7 @@ public class ReactHostImpl implements ReactHost {
raiseSoftException(method, reason, ex);
ReactInstanceTaskUnwrapper reactInstanceTaskUnwrapper =
createReactInstanceUnwraper("Destroy", method, reason);
createReactInstanceUnwrapper("Destroy", method, reason);
if (mDestroyTask == null) {
mDestroyTask =
@@ -9,7 +9,7 @@ package com.facebook.react.runtime
import com.facebook.jni.HybridData
import com.facebook.proguard.annotations.DoNotStripAny
import com.facebook.react.runtime.internal.bolts.Task
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.soloader.SoLoader
import java.io.Closeable
import java.util.concurrent.Executor
@@ -19,8 +19,7 @@ internal class ReactHostInspectorTarget(private val reactHostImpl: ReactHostImpl
// fbjni looks for the exact name "mHybridData":
// https://github.com/facebookincubator/fbjni/blob/5587a7fd2b191656be9391a3832ce04c034009a5/cxx/fbjni/detail/Hybrid.h#L310
@Suppress("NoHungarianNotation")
private val mHybridData: HybridData =
initHybrid(reactHostImpl, Task.UI_THREAD_CONDITIONAL_SYNC_EXECUTOR)
private val mHybridData: HybridData = initHybrid(reactHostImpl, UIThreadConditionalSyncExecutor())
private external fun initHybrid(reactHostImpl: ReactHostImpl, executor: Executor): HybridData
@@ -39,4 +38,20 @@ internal class ReactHostInspectorTarget(private val reactHostImpl: ReactHostImpl
SoLoader.loadLibrary("rninstance")
}
}
/**
* An [java.util.concurrent.Executor] that runs tasks on the UI thread (immediately if already on
* that thread).
*/
private class UIThreadConditionalSyncExecutor : Executor {
override fun execute(command: Runnable) {
if (UiThreadUtil.isOnUiThread()) {
// If we're already on the main thread, execute the command immediately
command.run()
} else {
// Otherwise, post it on the main thread handler
UiThreadUtil.runOnUiThread(command)
}
}
}
}
@@ -1,133 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Aggregates multiple {@code Throwable}s that may be thrown in the process of a task's execution.
*
* @see Task#whenAll(java.util.Collection)
*/
class AggregateException extends Exception {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_MESSAGE = "There were multiple errors.";
@NonNull private final List<Throwable> innerThrowables;
/**
* Constructs a new {@code AggregateException} with the current stack trace, the specified detail
* message and with references to the inner throwables that are the cause of this exception.
*
* @param detailMessage The detail message for this exception.
* @param innerThrowables The exceptions that are the cause of the current exception.
*/
public AggregateException(@NonNull String detailMessage, @NonNull Throwable[] innerThrowables) {
this(detailMessage, Arrays.asList(innerThrowables));
}
/**
* Constructs a new {@code AggregateException} with the current stack trace, the specified detail
* message and with references to the inner throwables that are the cause of this exception.
*
* @param detailMessage The detail message for this exception.
* @param innerThrowables The exceptions that are the cause of the current exception.
*/
public AggregateException(
@NonNull String detailMessage, @Nullable List<? extends Throwable> innerThrowables) {
super(
detailMessage,
innerThrowables != null && innerThrowables.size() > 0 ? innerThrowables.get(0) : null);
this.innerThrowables =
Collections.unmodifiableList(innerThrowables != null ? innerThrowables : new ArrayList<>());
}
/**
* Constructs a new {@code AggregateException} with the current stack trace and with references to
* the inner throwables that are the cause of this exception.
*
* @param innerThrowables The exceptions that are the cause of the current exception.
*/
public AggregateException(@Nullable List<? extends Throwable> innerThrowables) {
this(DEFAULT_MESSAGE, innerThrowables);
}
/**
* Returns a read-only {@link List} of the {@link Throwable} instances that caused the current
* exception.
*/
public @NonNull List<Throwable> getInnerThrowables() {
return innerThrowables;
}
@Override
public void printStackTrace(@NonNull PrintStream err) {
super.printStackTrace(err);
int currentIndex = -1;
for (Throwable throwable : innerThrowables) {
err.append("\n");
err.append(" Inner throwable #");
err.append(Integer.toString(++currentIndex));
err.append(": ");
throwable.printStackTrace(err);
err.append("\n");
}
}
@Override
public void printStackTrace(@NonNull PrintWriter err) {
super.printStackTrace(err);
int currentIndex = -1;
for (Throwable throwable : innerThrowables) {
err.append("\n");
err.append(" Inner throwable #");
err.append(Integer.toString(++currentIndex));
err.append(": ");
throwable.printStackTrace(err);
err.append("\n");
}
}
/**
* @deprecated Please use {@link #getInnerThrowables()} instead.
*/
@Deprecated
public @NonNull List<Exception> getErrors() {
List<Exception> errors = new ArrayList<Exception>();
if (innerThrowables == null) {
return errors;
}
for (Throwable cause : innerThrowables) {
if (cause instanceof Exception) {
errors.add((Exception) cause);
} else {
errors.add(new Exception(cause));
}
}
return errors;
}
/**
* @deprecated Please use {@link #getInnerThrowables()} instead.
*/
@Deprecated
public @NonNull Throwable[] getCauses() {
return innerThrowables.toArray(new Throwable[innerThrowables.size()]);
}
}
@@ -1,158 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* This was created because the helper methods in {@link java.util.concurrent.Executors} do not work
* as people would normally expect.
*
* <p>Normally, you would think that a cached thread pool would create new threads when necessary,
* queue them when the pool is full, and kill threads when they've been inactive for a certain
* period of time. This is not how {@link java.util.concurrent.Executors#newCachedThreadPool()}
* works.
*
* <p>Instead, {@link java.util.concurrent.Executors#newCachedThreadPool()} executes all tasks on a
* new or cached thread immediately because corePoolSize is 0, SynchronousQueue is a queue with size
* 0 and maxPoolSize is Integer.MAX_VALUE. This is dangerous because it can create an unchecked
* amount of threads.
*/
/* package */
final class AndroidExecutors {
private static final AndroidExecutors INSTANCE = new AndroidExecutors();
@NonNull private final Executor uiThread;
@NonNull private final Executor uiThreadConditionalSync;
private AndroidExecutors() {
uiThread = new UIThreadExecutor();
uiThreadConditionalSync = new UIThreadConditionalSyncExecutor();
}
/**
* Nexus 5: Quad-Core Moto X: Dual-Core
*
* <p>AsyncTask: CORE_POOL_SIZE = CPU_COUNT + 1 MAX_POOL_SIZE = CPU_COUNT * 2 + 1
*
* <p>https://github.com/android/platform_frameworks_base/commit/719c44e03b97e850a46136ba336d729f5fbd1f47
*/
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
/* package */ static final int CORE_POOL_SIZE = CPU_COUNT + 1;
/* package */ static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
/* package */ static final long KEEP_ALIVE_TIME = 1L;
/**
* Creates a proper Cached Thread Pool. Tasks will reuse cached threads if available or create new
* threads until the core pool is full. tasks will then be queued. If an task cannot be queued, a
* new thread will be created unless this would exceed max pool size, then the task will be
* rejected. Threads will time out after 1 second.
*
* <p>Core thread timeout is only available on android-9+.
*
* @return the newly created thread pool
*/
public static @NonNull ExecutorService newCachedThreadPool() {
ThreadPoolExecutor executor =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
executor.allowCoreThreadTimeOut(true);
return executor;
}
/**
* Creates a proper Cached Thread Pool. Tasks will reuse cached threads if available or create new
* threads until the core pool is full. tasks will then be queued. If an task cannot be queued, a
* new thread will be created unless this would exceed max pool size, then the task will be
* rejected. Threads will time out after 1 second.
*
* <p>Core thread timeout is only available on android-9+.
*
* @param threadFactory the factory to use when creating new threads
* @return the newly created thread pool
*/
public static @NonNull ExecutorService newCachedThreadPool(@NonNull ThreadFactory threadFactory) {
ThreadPoolExecutor executor =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
executor.allowCoreThreadTimeOut(true);
return executor;
}
/**
* An {@link java.util.concurrent.Executor} that schedules tasks to run asynchronously on the UI
* thread.
*/
public static @NonNull Executor uiThread() {
return INSTANCE.uiThread;
}
/**
* An {@link java.util.concurrent.Executor} that runs tasks on the UI thread (immediately if
* already on that thread).
*/
public static @NonNull Executor uiThreadConditionalSync() {
return INSTANCE.uiThreadConditionalSync;
}
/**
* An {@link java.util.concurrent.Executor} that schedules tasks to run asynchronously on the UI
* thread.
*/
private static class UIThreadExecutor implements Executor {
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
// Otherwise, post it on the main thread handler
mHandler.post(command);
}
}
/**
* An {@link java.util.concurrent.Executor} that runs tasks on the UI thread (immediately if
* already on that thread).
*/
private static class UIThreadConditionalSyncExecutor implements Executor {
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
if (Looper.myLooper() == mHandler.getLooper()) {
// If we're already on the main thread, execute the command immediately
command.run();
} else {
// Otherwise, post it on the main thread handler
mHandler.post(command);
}
}
}
}
@@ -1,120 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import androidx.annotation.NonNull;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
/** Collection of {@link Executor}s to use in conjunction with {@link Task}. */
/* package */
final class BoltsExecutors {
private static final BoltsExecutors INSTANCE = new BoltsExecutors();
private static boolean isAndroidRuntime() {
String javaRuntimeName = System.getProperty("java.runtime.name");
if (javaRuntimeName == null) {
return false;
}
return javaRuntimeName.toLowerCase(Locale.US).contains("android");
}
private final @NonNull ExecutorService background;
private final @NonNull ScheduledExecutorService scheduled;
private final @NonNull Executor immediate;
private BoltsExecutors() {
background =
!isAndroidRuntime()
? java.util.concurrent.Executors.newCachedThreadPool()
: AndroidExecutors.newCachedThreadPool();
scheduled = Executors.newSingleThreadScheduledExecutor();
immediate = new ImmediateExecutor();
}
/** An {@link java.util.concurrent.Executor} that executes tasks in parallel. */
public static @NonNull ExecutorService background() {
return INSTANCE.background;
}
/* package */ static @NonNull ScheduledExecutorService scheduled() {
return INSTANCE.scheduled;
}
/**
* An {@link java.util.concurrent.Executor} that executes tasks in the current thread unless the
* stack runs too deep, at which point it will delegate to {@link BoltsExecutors#background} in
* order to trim the stack.
*/
/* package */ static @NonNull Executor immediate() {
return INSTANCE.immediate;
}
/**
* An {@link java.util.concurrent.Executor} that runs a runnable inline (rather than scheduling it
* on a thread pool) as long as the recursion depth is less than MAX_DEPTH. If the executor has
* recursed too deeply, it will instead delegate to the {@link Task#BACKGROUND_EXECUTOR} in order
* to trim the stack.
*/
private static class ImmediateExecutor implements Executor {
private static final int MAX_DEPTH = 15;
private ThreadLocal<Integer> executionDepth = new ThreadLocal<>();
/**
* Increments the depth.
*
* @return the new depth value.
*/
private int incrementDepth() {
Integer oldDepth = executionDepth.get();
if (oldDepth == null) {
oldDepth = 0;
}
int newDepth = oldDepth + 1;
executionDepth.set(newDepth);
return newDepth;
}
/**
* Decrements the depth.
*
* @return the new depth value.
*/
private int decrementDepth() {
Integer oldDepth = executionDepth.get();
if (oldDepth == null) {
oldDepth = 0;
}
int newDepth = oldDepth - 1;
if (newDepth == 0) {
executionDepth.remove();
} else {
executionDepth.set(newDepth);
}
return newDepth;
}
@Override
public void execute(@NonNull Runnable command) {
int depth = incrementDepth();
try {
if (depth <= MAX_DEPTH) {
command.run();
} else {
BoltsExecutors.background().execute(command);
}
} finally {
decrementDepth();
}
}
}
}
@@ -1,74 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import androidx.annotation.NonNull;
import java.util.Locale;
import java.util.concurrent.CancellationException;
/**
* Propagates notification that operations should be canceled.
*
* <p>Create an instance of {@code CancellationTokenSource} and pass the token returned from {@code
* CancellationTokenSource#getToken()} to the asynchronous operation(s). Call {@code
* CancellationTokenSource#cancel()} to cancel the operations.
*
* <p>A {@code CancellationToken} can only be cancelled once - it should not be passed to future
* operations once cancelled.
*
* @see CancellationTokenSource
* @see CancellationTokenSource#getToken()
* @see CancellationTokenSource#cancel()
* @see CancellationToken#register(Runnable)
*/
public class CancellationToken {
private final CancellationTokenSource tokenSource;
/* package */ CancellationToken(@NonNull CancellationTokenSource tokenSource) {
this.tokenSource = tokenSource;
}
/**
* @return {@code true} if the cancellation was requested from the source, {@code false}
* otherwise.
*/
public boolean isCancellationRequested() {
return tokenSource.isCancellationRequested();
}
/**
* Registers a runnable that will be called when this CancellationToken is canceled. If this token
* is already in the canceled state, the runnable will be run immediately and synchronously.
*
* @param action the runnable to be run when the token is cancelled.
* @return a {@link CancellationTokenRegistration} instance that can be used to unregister the
* action.
*/
public @NonNull CancellationTokenRegistration register(@NonNull Runnable action) {
return tokenSource.register(action);
}
/**
* @throws CancellationException if this token has had cancellation requested. May be used to stop
* execution of a thread or runnable.
*/
public void throwIfCancellationRequested() throws CancellationException {
tokenSource.throwIfCancellationRequested();
}
@Override
public String toString() {
return String.format(
Locale.US,
"%s@%s[cancellationRequested=%s]",
getClass().getName(),
Integer.toHexString(hashCode()),
Boolean.toString(tokenSource.isCancellationRequested()));
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Closeable;
/**
* Represents a callback delegate that has been registered with a {@link CancellationToken}.
*
* @see CancellationToken#register(Runnable)
*/
class CancellationTokenRegistration implements Closeable {
private final Object lock = new Object();
private @Nullable CancellationTokenSource tokenSource;
private @Nullable Runnable action;
private boolean closed;
/* package */ CancellationTokenRegistration(
@NonNull CancellationTokenSource tokenSource, @NonNull Runnable action) {
this.tokenSource = tokenSource;
this.action = action;
}
/** Unregisters the callback runnable from the cancellation token. */
@Override
public void close() {
synchronized (lock) {
if (closed) {
return;
}
closed = true;
tokenSource.unregister(this);
tokenSource = null;
action = null;
}
}
/* package */ void runAction() {
synchronized (lock) {
throwIfClosed();
action.run();
close();
}
}
private void throwIfClosed() {
if (closed) {
throw new IllegalStateException("Object already closed");
}
}
}
@@ -1,218 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Signals to a {@link CancellationToken} that it should be canceled. To create a {@code
* CancellationToken} first create a {@code CancellationTokenSource} then call {@link #getToken()}
* to retrieve the token for the source.
*
* @see CancellationToken
* @see CancellationTokenSource#getToken()
*/
public class CancellationTokenSource implements Closeable {
private final Object lock = new Object();
private final @NonNull List<CancellationTokenRegistration> registrations = new ArrayList<>();
private final @NonNull ScheduledExecutorService executor = BoltsExecutors.scheduled();
@Nullable private ScheduledFuture<?> scheduledCancellation;
private boolean cancellationRequested;
private boolean closed;
/** Create a new {@code CancellationTokenSource}. */
public CancellationTokenSource() {}
/**
* @return {@code true} if cancellation has been requested for this {@code
* CancellationTokenSource}.
*/
public boolean isCancellationRequested() {
synchronized (lock) {
throwIfClosed();
return cancellationRequested;
}
}
/**
* @return the token that can be passed to asynchronous method to control cancellation.
*/
public @NonNull CancellationToken getToken() {
synchronized (lock) {
throwIfClosed();
return new CancellationToken(this);
}
}
/** Cancels the token if it has not already been cancelled. */
public void cancel() {
List<CancellationTokenRegistration> registrations;
synchronized (lock) {
throwIfClosed();
if (cancellationRequested) {
return;
}
cancelScheduledCancellation();
cancellationRequested = true;
registrations = new ArrayList<>(this.registrations);
}
notifyListeners(registrations);
}
/**
* Schedules a cancel operation on this {@code CancellationTokenSource} after the specified number
* of milliseconds.
*
* @param delay The number of milliseconds to wait before completing the returned task. If delay
* is {@code 0} the cancel is executed immediately. If delay is {@code -1} any scheduled
* cancellation is stopped.
*/
public void cancelAfter(final long delay) {
cancelAfter(delay, TimeUnit.MILLISECONDS);
}
private void cancelAfter(long delay, @NonNull TimeUnit timeUnit) {
if (delay < -1) {
throw new IllegalArgumentException("Delay must be >= -1");
}
if (delay == 0) {
cancel();
return;
}
synchronized (lock) {
if (cancellationRequested) {
return;
}
cancelScheduledCancellation();
if (delay != -1) {
scheduledCancellation =
executor.schedule(
new Runnable() {
@Override
public void run() {
synchronized (lock) {
scheduledCancellation = null;
}
cancel();
}
},
delay,
timeUnit);
}
}
}
@Override
public void close() {
synchronized (lock) {
if (closed) {
return;
}
cancelScheduledCancellation();
List<CancellationTokenRegistration> registrations = new ArrayList<>(this.registrations);
for (CancellationTokenRegistration registration : registrations) {
registration.close();
}
this.registrations.clear();
closed = true;
}
}
/* package */ @NonNull
CancellationTokenRegistration register(@NonNull Runnable action) {
CancellationTokenRegistration ctr;
synchronized (lock) {
throwIfClosed();
ctr = new CancellationTokenRegistration(this, action);
if (cancellationRequested) {
ctr.runAction();
} else {
registrations.add(ctr);
}
}
return ctr;
}
/**
* @throws CancellationException if this token has had cancellation requested. May be used to stop
* execution of a thread or runnable.
*/
/* package */ void throwIfCancellationRequested() throws CancellationException {
synchronized (lock) {
throwIfClosed();
if (cancellationRequested) {
throw new CancellationException();
}
}
}
/* package */ void unregister(@NonNull CancellationTokenRegistration registration) {
synchronized (lock) {
throwIfClosed();
registrations.remove(registration);
}
}
// This method makes no attempt to perform any synchronization or state checks itself and once
// invoked will notify all runnables unconditionally. As such if you require the notification
// event
// to be synchronized with state changes you should provide external synchronization.
// If this is invoked without external synchronization there is a probability the token becomes
// cancelled concurrently.
private void notifyListeners(@NonNull List<CancellationTokenRegistration> registrations) {
for (CancellationTokenRegistration registration : registrations) {
registration.runAction();
}
}
@Override
public String toString() {
return String.format(
Locale.US,
"%s@%s[cancellationRequested=%s]",
getClass().getName(),
Integer.toHexString(hashCode()),
Boolean.toString(isCancellationRequested()));
}
// This method makes no attempt to perform any synchronization itself - you should ensure
// accesses to this method are synchronized if you want to ensure correct behaviour in the
// face of a concurrent invocation of the close method.
private void throwIfClosed() {
if (closed) {
throw new IllegalStateException("Object already closed");
}
}
// Performs no synchronization.
private void cancelScheduledCancellation() {
if (scheduledCancellation != null) {
scheduledCancellation.cancel(true);
scheduledCancellation = null;
}
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import androidx.annotation.Nullable;
/**
* Provides a class that can be used for capturing variables in an anonymous class implementation.
*
* @param <T>
*/
class Capture<T> {
private @Nullable T value;
public Capture() {}
public Capture(@Nullable T value) {
this.value = value;
}
public @Nullable T get() {
return value;
}
public void set(@Nullable T value) {
this.value = value;
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.runtime.internal.bolts;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.UiThreadUtil;
import java.util.concurrent.Executor;
/**
* This was created because the helper methods in {@link java.util.concurrent.Executors} do not work
* as people would normally expect.
*
* <p>Normally, you would think that a cached thread pool would create new threads when necessary,
* queue them when the pool is full, and kill threads when they've been inactive for a certain
* period of time. This is not how {@link java.util.concurrent.Executors#newCachedThreadPool()}
* works.
*
* <p>Instead, {@link java.util.concurrent.Executors#newCachedThreadPool()} executes all tasks on a
* new or cached thread immediately because corePoolSize is 0, SynchronousQueue is a queue with size
* 0 and maxPoolSize is Integer.MAX_VALUE. This is dangerous because it can create an unchecked
* amount of threads.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
/* package */
final class Executors {
public static final Executor UI_THREAD = new UIThreadExecutor();
public static final Executor IMMEDIATE = new ImmediateExecutor();
private static class UIThreadExecutor implements Executor {
@Override
public void execute(Runnable command) {
// Otherwise, post it on the main thread handler
UiThreadUtil.runOnUiThread(command);
}
}
/**
* An {@link java.util.concurrent.Executor} that schedules tasks to run asynchronously on the UI
* thread.
*/
private static class ImmediateExecutor implements Executor {
@Override
public void execute(Runnable command) {
command.run();
}
}
}
@@ -10,18 +10,11 @@ package com.facebook.react.runtime.internal.bolts;
import androidx.annotation.Nullable;
import com.facebook.react.interfaces.TaskInterface;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents the result of an asynchronous operation.
@@ -29,22 +22,15 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param <TResult> The type of the result of the task.
*/
public class Task<TResult> implements TaskInterface<TResult> {
/** An {@link java.util.concurrent.Executor} that executes tasks in parallel. */
public static final ExecutorService BACKGROUND_EXECUTOR = BoltsExecutors.background();
/**
* An {@link java.util.concurrent.Executor} that executes tasks in the current thread unless the
* stack runs too deep, at which point it will delegate to {@link Task#BACKGROUND_EXECUTOR} in
* order to trim the stack.
*/
private static final Executor IMMEDIATE_EXECUTOR = BoltsExecutors.immediate();
private static final Executor IMMEDIATE_EXECUTOR = Executors.IMMEDIATE;
/** An {@link java.util.concurrent.Executor} that executes tasks on the UI thread. */
public static final Executor UI_THREAD_EXECUTOR = AndroidExecutors.uiThread();
/** An {@link java.util.concurrent.Executor} that executes tasks on the UI thread. */
public static final Executor UI_THREAD_CONDITIONAL_SYNC_EXECUTOR =
AndroidExecutors.uiThreadConditionalSync();
public static final Executor UI_THREAD_EXECUTOR = Executors.UI_THREAD;
/**
* Interface for handlers invoked when a failed {@code Task} is about to be finalized, but the
@@ -225,74 +211,6 @@ public class Task<TResult> implements TaskInterface<TResult> {
return (Task<TResult>) TASK_CANCELLED;
}
/**
* Creates a task that completes after a time delay.
*
* @param delay The number of milliseconds to wait before completing the returned task. Zero and
* negative values are treated as requests for immediate execution.
*/
public static Task<Void> delay(long delay) {
return delay(delay, BoltsExecutors.scheduled(), null);
}
/**
* Creates a task that completes after a time delay.
*
* @param delay The number of milliseconds to wait before completing the returned task. Zero and
* negative values are treated as requests for immediate execution.
* @param cancellationToken The optional cancellation token that will be checked prior to
* completing the returned task.
*/
public static Task<Void> delay(long delay, CancellationToken cancellationToken) {
return delay(delay, BoltsExecutors.scheduled(), cancellationToken);
}
/* package */ static Task<Void> delay(
long delay, ScheduledExecutorService executor, final CancellationToken cancellationToken) {
if (cancellationToken != null && cancellationToken.isCancellationRequested()) {
return Task.cancelled();
}
if (delay <= 0) {
return Task.forResult(null);
}
final TaskCompletionSource<Void> tcs = new TaskCompletionSource<>();
final ScheduledFuture<?> scheduled =
executor.schedule(
new Runnable() {
@Override
public void run() {
tcs.trySetResult(null);
}
},
delay,
TimeUnit.MILLISECONDS);
if (cancellationToken != null) {
cancellationToken.register(
new Runnable() {
@Override
public void run() {
scheduled.cancel(true);
tcs.trySetCancelled();
}
});
}
return tcs.getTask();
}
/**
* Makes a fluent cast of a Task's result possible, avoiding an extra continuation just to cast
* the type of the result.
*/
public <TOut> Task<TOut> cast() {
@SuppressWarnings("unchecked")
Task<TOut> task = (Task<TOut>) this;
return task;
}
/** Turns a Task<T> into a Task<Void>, dropping any result. */
public Task<Void> makeVoid() {
return this.continueWithTask(
@@ -310,46 +228,14 @@ public class Task<TResult> implements TaskInterface<TResult> {
});
}
/**
* Invokes the callable on a background thread, returning a Task to represent the operation.
*
* <p>If you want to cancel the resulting Task throw a {@link
* java.util.concurrent.CancellationException} from the callable.
*/
public static <TResult> Task<TResult> callInBackground(Callable<TResult> callable) {
return call(callable, BACKGROUND_EXECUTOR, null);
}
/** Invokes the callable on a background thread, returning a Task to represent the operation. */
public static <TResult> Task<TResult> callInBackground(
Callable<TResult> callable, CancellationToken ct) {
return call(callable, BACKGROUND_EXECUTOR, ct);
}
/**
* Invokes the callable using the given executor, returning a Task to represent the operation.
*
* <p>If you want to cancel the resulting Task throw a {@link
* java.util.concurrent.CancellationException} from the callable.
*/
public static <TResult> Task<TResult> call(final Callable<TResult> callable, Executor executor) {
return call(callable, executor, null);
}
/** Invokes the callable using the given executor, returning a Task to represent the operation. */
public static <TResult> Task<TResult> call(
final Callable<TResult> callable, Executor executor, final CancellationToken ct) {
public static <TResult> Task<TResult> call(final Callable<TResult> callable, Executor executor) {
final TaskCompletionSource<TResult> tcs = new TaskCompletionSource<>();
try {
executor.execute(
new Runnable() {
@Override
public void run() {
if (ct != null && ct.isCancellationRequested()) {
tcs.setCancelled();
return;
}
try {
tcs.setResult(callable.call());
} catch (CancellationException e) {
@@ -373,266 +259,7 @@ public class Task<TResult> implements TaskInterface<TResult> {
* java.util.concurrent.CancellationException} from the callable.
*/
public static <TResult> Task<TResult> call(final Callable<TResult> callable) {
return call(callable, IMMEDIATE_EXECUTOR, null);
}
/** Invokes the callable on the current thread, producing a Task. */
public static <TResult> Task<TResult> call(
final Callable<TResult> callable, CancellationToken ct) {
return call(callable, IMMEDIATE_EXECUTOR, ct);
}
/**
* Creates a task that will complete when any of the supplied tasks have completed.
*
* <p>The returned task will complete when any of the supplied tasks has completed. The returned
* task will always end in the completed state with its result set to the first task to complete.
* This is true even if the first task to complete ended in the canceled or faulted state.
*
* @param tasks The tasks to wait on for completion.
* @return A task that represents the completion of one of the supplied tasks. The return task's
* result is the task that completed.
*/
public static <TResult> Task<Task<TResult>> whenAnyResult(
Collection<? extends Task<TResult>> tasks) {
if (tasks.size() == 0) {
return Task.forResult(null);
}
final TaskCompletionSource<Task<TResult>> firstCompleted = new TaskCompletionSource<>();
final AtomicBoolean isAnyTaskComplete = new AtomicBoolean(false);
for (Task<TResult> task : tasks) {
task.continueWith(
new Continuation<TResult, Void>() {
@Override
public Void then(Task<TResult> task) {
if (isAnyTaskComplete.compareAndSet(false, true)) {
firstCompleted.setResult(task);
} else {
Throwable ensureObserved = task.getError();
}
return null;
}
});
}
return firstCompleted.getTask();
}
/**
* Creates a task that will complete when any of the supplied tasks have completed.
*
* <p>The returned task will complete when any of the supplied tasks has completed. The returned
* task will always end in the completed state with its result set to the first task to complete.
* This is true even if the first task to complete ended in the canceled or faulted state.
*
* @param tasks The tasks to wait on for completion.
* @return A task that represents the completion of one of the supplied tasks. The return task's
* Result is the task that completed.
*/
@SuppressWarnings("unchecked")
public static Task<Task<?>> whenAny(Collection<? extends Task<?>> tasks) {
if (tasks.size() == 0) {
return Task.forResult(null);
}
final TaskCompletionSource<Task<?>> firstCompleted = new TaskCompletionSource<>();
final AtomicBoolean isAnyTaskComplete = new AtomicBoolean(false);
for (Task<?> task : tasks) {
((Task<Object>) task)
.continueWith(
new Continuation<Object, Void>() {
@Override
public Void then(Task<Object> task) {
if (isAnyTaskComplete.compareAndSet(false, true)) {
firstCompleted.setResult(task);
} else {
Throwable ensureObserved = task.getError();
}
return null;
}
});
}
return firstCompleted.getTask();
}
/**
* Creates a task that completes when all of the provided tasks are complete.
*
* <p>If any of the supplied tasks completes in a faulted state, the returned task will also
* complete in a faulted state, where its exception will resolve to that {@link
* java.lang.Exception} if a single task fails or an {@link AggregateException} of all the {@link
* java.lang.Exception}s if multiple tasks fail.
*
* <p>If none of the supplied tasks faulted but at least one of them was cancelled, the returned
* task will end as cancelled.
*
* <p>If none of the tasks faulted and none of the tasks were cancelled, the resulting task will
* end completed. The result of the returned task will be set to a list containing all of the
* results of the supplied tasks in the same order as they were provided (e.g. if the input tasks
* collection contained t1, t2, t3, the output task's result will return an {@code
* List&lt;TResult&gt;} where {@code list.get(0) == t1.getResult(), list.get(1) == t2.getResult(),
* and list.get(2) == t3.getResult()}).
*
* <p>If the supplied collection contains no tasks, the returned task will immediately transition
* to a completed state before it's returned to the caller. The returned {@code
* List&lt;TResult&gt;} will contain 0 elements.
*
* @param tasks The tasks that the return value will wait for before completing.
* @return A Task that will resolve to {@code List&lt;TResult&gt;} when all the tasks are
* resolved.
*/
public static <TResult> Task<List<TResult>> whenAllResult(
final Collection<? extends Task<TResult>> tasks) {
return whenAll(tasks)
.onSuccess(
new Continuation<Void, List<TResult>>() {
@Override
public List<TResult> then(Task<Void> task) throws Exception {
if (tasks.size() == 0) {
return Collections.emptyList();
}
List<TResult> results = new ArrayList<>();
for (Task<TResult> individualTask : tasks) {
results.add(individualTask.getResult());
}
return results;
}
});
}
/**
* Creates a task that completes when all of the provided tasks are complete.
*
* <p>If any of the supplied tasks completes in a faulted state, the returned task will also
* complete in a faulted state, where its exception will resolve to that {@link
* java.lang.Exception} if a single task fails or an {@link AggregateException} of all the {@link
* java.lang.Exception}s if multiple tasks fail.
*
* <p>If none of the supplied tasks faulted but at least one of them was cancelled, the returned
* task will end as cancelled.
*
* <p>If none of the tasks faulted and none of the tasks were canceled, the resulting task will
* end in the completed state.
*
* <p>If the supplied collection contains no tasks, the returned task will immediately transition
* to a completed state before it's returned to the caller.
*
* @param tasks The tasks that the return value will wait for before completing.
* @return A Task that will resolve to {@code Void} when all the tasks are resolved.
*/
public static Task<Void> whenAll(Collection<? extends Task<?>> tasks) {
if (tasks.size() == 0) {
return Task.forResult(null);
}
final TaskCompletionSource<Void> allFinished = new TaskCompletionSource<>();
final ArrayList<Exception> causes = new ArrayList<>();
final Object errorLock = new Object();
final AtomicInteger count = new AtomicInteger(tasks.size());
final AtomicBoolean isCancelled = new AtomicBoolean(false);
for (Task<?> task : tasks) {
@SuppressWarnings("unchecked")
Task<Object> t = (Task<Object>) task;
t.continueWith(
new Continuation<Object, Void>() {
@Override
public Void then(Task<Object> task) {
if (task.isFaulted()) {
synchronized (errorLock) {
causes.add(task.getError());
}
}
if (task.isCancelled()) {
isCancelled.set(true);
}
if (count.decrementAndGet() == 0) {
if (causes.size() != 0) {
if (causes.size() == 1) {
allFinished.setError(causes.get(0));
} else {
Exception error =
new AggregateException(
String.format("There were %d exceptions.", causes.size()), causes);
allFinished.setError(error);
}
} else if (isCancelled.get()) {
allFinished.setCancelled();
} else {
allFinished.setResult(null);
}
}
return null;
}
});
}
return allFinished.getTask();
}
/**
* Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
* a task continuation.
*/
public Task<Void> continueWhile(
Callable<Boolean> predicate, Continuation<Void, Task<Void>> continuation) {
return continueWhile(predicate, continuation, IMMEDIATE_EXECUTOR, null);
}
/**
* Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
* a task continuation.
*/
public Task<Void> continueWhile(
Callable<Boolean> predicate,
Continuation<Void, Task<Void>> continuation,
CancellationToken ct) {
return continueWhile(predicate, continuation, IMMEDIATE_EXECUTOR, ct);
}
/**
* Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
* a task continuation.
*/
public Task<Void> continueWhile(
final Callable<Boolean> predicate,
final Continuation<Void, Task<Void>> continuation,
final Executor executor) {
return continueWhile(predicate, continuation, executor, null);
}
/**
* Continues a task with the equivalent of a Task-based while loop, where the body of the loop is
* a task continuation.
*/
public Task<Void> continueWhile(
final Callable<Boolean> predicate,
final Continuation<Void, Task<Void>> continuation,
final Executor executor,
final CancellationToken ct) {
final Capture<Continuation<Void, Task<Void>>> predicateContinuation = new Capture<>();
predicateContinuation.set(
new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
if (ct != null && ct.isCancellationRequested()) {
return Task.cancelled();
}
if (predicate.call()) {
return Task.<Void>forResult(null)
.onSuccessTask(continuation, executor)
.onSuccessTask(predicateContinuation.get(), executor);
}
return Task.forResult(null);
}
});
return makeVoid().continueWithTask(predicateContinuation.get(), executor);
return call(callable, IMMEDIATE_EXECUTOR);
}
/**
@@ -642,18 +269,6 @@ public class Task<TResult> implements TaskInterface<TResult> {
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(
final Continuation<TResult, TContinuationResult> continuation, final Executor executor) {
return continueWith(continuation, executor, null);
}
/**
* Adds a continuation that will be scheduled using the executor, returning a new task that
* completes after the continuation has finished running. This allows the continuation to be
* scheduled on different thread.
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(
final Continuation<TResult, TContinuationResult> continuation,
final Executor executor,
final CancellationToken ct) {
boolean completed;
final TaskCompletionSource<TContinuationResult> tcs = new TaskCompletionSource<>();
synchronized (lock) {
@@ -663,14 +278,14 @@ public class Task<TResult> implements TaskInterface<TResult> {
new Continuation<TResult, Void>() {
@Override
public Void then(Task<TResult> task) {
completeImmediately(tcs, continuation, task, executor, ct);
completeImmediately(tcs, continuation, task, executor);
return null;
}
});
}
}
if (completed) {
completeImmediately(tcs, continuation, this, executor, ct);
completeImmediately(tcs, continuation, this, executor);
}
return tcs.getTask();
}
@@ -681,16 +296,7 @@ public class Task<TResult> implements TaskInterface<TResult> {
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(
Continuation<TResult, TContinuationResult> continuation) {
return continueWith(continuation, IMMEDIATE_EXECUTOR, null);
}
/**
* Adds a synchronous continuation to this task, returning a new task that completes after the
* continuation has finished running.
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(
Continuation<TResult, TContinuationResult> continuation, CancellationToken ct) {
return continueWith(continuation, IMMEDIATE_EXECUTOR, ct);
return continueWith(continuation, IMMEDIATE_EXECUTOR);
}
/**
@@ -700,17 +306,6 @@ public class Task<TResult> implements TaskInterface<TResult> {
public <TContinuationResult> Task<TContinuationResult> continueWithTask(
final Continuation<TResult, Task<TContinuationResult>> continuation,
final Executor executor) {
return continueWithTask(continuation, executor, null);
}
/**
* Adds an Task-based continuation to this task that will be scheduled using the executor,
* returning a new task that completes after the task returned by the continuation has completed.
*/
public <TContinuationResult> Task<TContinuationResult> continueWithTask(
final Continuation<TResult, Task<TContinuationResult>> continuation,
final Executor executor,
final CancellationToken ct) {
boolean completed;
final TaskCompletionSource<TContinuationResult> tcs = new TaskCompletionSource<>();
synchronized (lock) {
@@ -720,14 +315,14 @@ public class Task<TResult> implements TaskInterface<TResult> {
new Continuation<TResult, Void>() {
@Override
public Void then(Task<TResult> task) {
completeAfterTask(tcs, continuation, task, executor, ct);
completeAfterTask(tcs, continuation, task, executor);
return null;
}
});
}
}
if (completed) {
completeAfterTask(tcs, continuation, this, executor, ct);
completeAfterTask(tcs, continuation, this, executor);
}
return tcs.getTask();
}
@@ -738,16 +333,7 @@ public class Task<TResult> implements TaskInterface<TResult> {
*/
public <TContinuationResult> Task<TContinuationResult> continueWithTask(
Continuation<TResult, Task<TContinuationResult>> continuation) {
return continueWithTask(continuation, IMMEDIATE_EXECUTOR, null);
}
/**
* Adds an asynchronous continuation to this task, returning a new task that completes after the
* task returned by the continuation has completed.
*/
public <TContinuationResult> Task<TContinuationResult> continueWithTask(
Continuation<TResult, Task<TContinuationResult>> continuation, CancellationToken ct) {
return continueWithTask(continuation, IMMEDIATE_EXECUTOR, ct);
return continueWithTask(continuation, IMMEDIATE_EXECUTOR);
}
/**
@@ -756,25 +342,10 @@ public class Task<TResult> implements TaskInterface<TResult> {
*/
public <TContinuationResult> Task<TContinuationResult> onSuccess(
final Continuation<TResult, TContinuationResult> continuation, Executor executor) {
return onSuccess(continuation, executor, null);
}
/**
* Runs a continuation when a task completes successfully, forwarding along {@link
* java.lang.Exception} or cancellation.
*/
public <TContinuationResult> Task<TContinuationResult> onSuccess(
final Continuation<TResult, TContinuationResult> continuation,
Executor executor,
final CancellationToken ct) {
return continueWithTask(
new Continuation<TResult, Task<TContinuationResult>>() {
@Override
public Task<TContinuationResult> then(Task<TResult> task) {
if (ct != null && ct.isCancellationRequested()) {
return Task.cancelled();
}
if (task.isFaulted()) {
return Task.forError(task.getError());
} else if (task.isCancelled()) {
@@ -793,16 +364,7 @@ public class Task<TResult> implements TaskInterface<TResult> {
*/
public <TContinuationResult> Task<TContinuationResult> onSuccess(
final Continuation<TResult, TContinuationResult> continuation) {
return onSuccess(continuation, IMMEDIATE_EXECUTOR, null);
}
/**
* Runs a continuation when a task completes successfully, forwarding along {@link
* java.lang.Exception}s or cancellation.
*/
public <TContinuationResult> Task<TContinuationResult> onSuccess(
final Continuation<TResult, TContinuationResult> continuation, CancellationToken ct) {
return onSuccess(continuation, IMMEDIATE_EXECUTOR, ct);
return onSuccess(continuation, IMMEDIATE_EXECUTOR);
}
/**
@@ -811,25 +373,10 @@ public class Task<TResult> implements TaskInterface<TResult> {
*/
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(
final Continuation<TResult, Task<TContinuationResult>> continuation, Executor executor) {
return onSuccessTask(continuation, executor, null);
}
/**
* Runs a continuation when a task completes successfully, forwarding along {@link
* java.lang.Exception}s or cancellation.
*/
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(
final Continuation<TResult, Task<TContinuationResult>> continuation,
Executor executor,
final CancellationToken ct) {
return continueWithTask(
new Continuation<TResult, Task<TContinuationResult>>() {
@Override
public Task<TContinuationResult> then(Task<TResult> task) {
if (ct != null && ct.isCancellationRequested()) {
return Task.cancelled();
}
if (task.isFaulted()) {
return Task.forError(task.getError());
} else if (task.isCancelled()) {
@@ -851,15 +398,6 @@ public class Task<TResult> implements TaskInterface<TResult> {
return onSuccessTask(continuation, IMMEDIATE_EXECUTOR);
}
/**
* Runs a continuation when a task completes successfully, forwarding along {@link
* java.lang.Exception}s or cancellation.
*/
public <TContinuationResult> Task<TContinuationResult> onSuccessTask(
final Continuation<TResult, Task<TContinuationResult>> continuation, CancellationToken ct) {
return onSuccessTask(continuation, IMMEDIATE_EXECUTOR, ct);
}
/**
* Handles the non-async (i.e. the continuation doesn't return a Task) continuation case, passing
* the results of the given Task through to the given continuation and using the results of that
@@ -875,18 +413,12 @@ public class Task<TResult> implements TaskInterface<TResult> {
final TaskCompletionSource<TContinuationResult> tcs,
final Continuation<TResult, TContinuationResult> continuation,
final Task<TResult> task,
Executor executor,
final CancellationToken ct) {
Executor executor) {
try {
executor.execute(
new Runnable() {
@Override
public void run() {
if (ct != null && ct.isCancellationRequested()) {
tcs.setCancelled();
return;
}
try {
TContinuationResult result = continuation.then(task);
tcs.setResult(result);
@@ -918,18 +450,12 @@ public class Task<TResult> implements TaskInterface<TResult> {
final TaskCompletionSource<TContinuationResult> tcs,
final Continuation<TResult, Task<TContinuationResult>> continuation,
final Task<TResult> task,
final Executor executor,
final CancellationToken ct) {
final Executor executor) {
try {
executor.execute(
new Runnable() {
@Override
public void run() {
if (ct != null && ct.isCancellationRequested()) {
tcs.setCancelled();
return;
}
try {
Task<TContinuationResult> result = continuation.then(task);
if (result == null) {
@@ -939,11 +465,6 @@ public class Task<TResult> implements TaskInterface<TResult> {
new Continuation<TContinuationResult, Void>() {
@Override
public Void then(Task<TContinuationResult> task) {
if (ct != null && ct.isCancellationRequested()) {
tcs.setCancelled();
return null;
}
if (task.isCancelled()) {
tcs.setCancelled();
} else if (task.isFaulted()) {