diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 03d6e5bf89b..ba5759635d5 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 ()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 { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BoltsFutureTask.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BoltsFutureTask.java deleted file mode 100644 index 04a9e8a97b7..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BoltsFutureTask.java +++ /dev/null @@ -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 The type of the result of the task. - */ -class BoltsFutureTask implements Future { - private final Task mTask; - private boolean isTaskCancelled = false; - private final CancellationTokenSource mCancellationTokenSource; - - private BoltsFutureTask(Task 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 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 task) throws ExecutionException { - if (task.isFaulted()) { - throw new ExecutionException("", new Throwable()); - } else if (task.isCancelled()) { - throw new CancellationException(""); - } - return task.getResult(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java index 51410faba7c..e58483008be 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.java @@ -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> 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 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 callWithExistingReactInstance( final String callingMethod, final VeniceThenable 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 callAfterGetOrCreateReactInstance( final String callingMethod, final VeniceThenable runnable) { final String method = "callAfterGetOrCreateReactInstance(" + callingMethod + ")"; return getOrCreateReactInstance() .onSuccess( - (Continuation) - 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 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 = diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt index be72c5f4d18..c5aee5ea21e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt @@ -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) + } + } + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AggregateException.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AggregateException.java deleted file mode 100644 index 1c918d50a5f..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AggregateException.java +++ /dev/null @@ -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 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 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 innerThrowables) { - this(DEFAULT_MESSAGE, innerThrowables); - } - - /** - * Returns a read-only {@link List} of the {@link Throwable} instances that caused the current - * exception. - */ - public @NonNull List 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 getErrors() { - List errors = new ArrayList(); - 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()]); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AndroidExecutors.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AndroidExecutors.java deleted file mode 100644 index 688f697f700..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/AndroidExecutors.java +++ /dev/null @@ -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. - * - *

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. - * - *

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 - * - *

AsyncTask: CORE_POOL_SIZE = CPU_COUNT + 1 MAX_POOL_SIZE = CPU_COUNT * 2 + 1 - * - *

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. - * - *

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()); - - 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. - * - *

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(), - 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); - } - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/BoltsExecutors.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/BoltsExecutors.java deleted file mode 100644 index 8ea8b60b8b6..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/BoltsExecutors.java +++ /dev/null @@ -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 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(); - } - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationToken.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationToken.java deleted file mode 100644 index 09b6f59dae3..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationToken.java +++ /dev/null @@ -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. - * - *

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. - * - *

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())); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationTokenRegistration.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationTokenRegistration.java deleted file mode 100644 index be723f5e49e..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationTokenRegistration.java +++ /dev/null @@ -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"); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationTokenSource.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationTokenSource.java deleted file mode 100644 index d86f12a64c8..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/CancellationTokenSource.java +++ /dev/null @@ -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 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 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 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 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; - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Capture.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Capture.java deleted file mode 100644 index 47743a60e6f..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Capture.java +++ /dev/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 - */ -class Capture { - 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; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Executors.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Executors.java new file mode 100644 index 00000000000..3b7ee962a99 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Executors.java @@ -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. + * + *

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. + * + *

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(); + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java index 6939cdf0cb3..38cdfb5a80a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/internal/bolts/Task.java @@ -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 The type of the result of the task. */ public class Task implements TaskInterface { - /** 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 implements TaskInterface { return (Task) 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 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 delay(long delay, CancellationToken cancellationToken) { - return delay(delay, BoltsExecutors.scheduled(), cancellationToken); - } - - /* package */ static Task 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 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 Task cast() { - @SuppressWarnings("unchecked") - Task task = (Task) this; - return task; - } - /** Turns a Task into a Task, dropping any result. */ public Task makeVoid() { return this.continueWithTask( @@ -310,46 +228,14 @@ public class Task implements TaskInterface { }); } - /** - * Invokes the callable on a background thread, returning a Task to represent the operation. - * - *

If you want to cancel the resulting Task throw a {@link - * java.util.concurrent.CancellationException} from the callable. - */ - public static Task callInBackground(Callable callable) { - return call(callable, BACKGROUND_EXECUTOR, null); - } - - /** Invokes the callable on a background thread, returning a Task to represent the operation. */ - public static Task callInBackground( - Callable callable, CancellationToken ct) { - return call(callable, BACKGROUND_EXECUTOR, ct); - } - - /** - * Invokes the callable using the given executor, returning a Task to represent the operation. - * - *

If you want to cancel the resulting Task throw a {@link - * java.util.concurrent.CancellationException} from the callable. - */ - public static Task call(final Callable callable, Executor executor) { - return call(callable, executor, null); - } - /** Invokes the callable using the given executor, returning a Task to represent the operation. */ - public static Task call( - final Callable callable, Executor executor, final CancellationToken ct) { + public static Task call(final Callable callable, Executor executor) { final TaskCompletionSource 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 implements TaskInterface { * java.util.concurrent.CancellationException} from the callable. */ public static Task call(final Callable callable) { - return call(callable, IMMEDIATE_EXECUTOR, null); - } - - /** Invokes the callable on the current thread, producing a Task. */ - public static Task call( - final Callable callable, CancellationToken ct) { - return call(callable, IMMEDIATE_EXECUTOR, ct); - } - - /** - * Creates a task that will complete when any of the supplied tasks have completed. - * - *

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 Task> whenAnyResult( - Collection> tasks) { - if (tasks.size() == 0) { - return Task.forResult(null); - } - - final TaskCompletionSource> firstCompleted = new TaskCompletionSource<>(); - final AtomicBoolean isAnyTaskComplete = new AtomicBoolean(false); - - for (Task task : tasks) { - task.continueWith( - new Continuation() { - @Override - public Void then(Task 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. - * - *

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> whenAny(Collection> tasks) { - if (tasks.size() == 0) { - return Task.forResult(null); - } - - final TaskCompletionSource> firstCompleted = new TaskCompletionSource<>(); - final AtomicBoolean isAnyTaskComplete = new AtomicBoolean(false); - - for (Task task : tasks) { - ((Task) task) - .continueWith( - new Continuation() { - @Override - public Void then(Task 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. - * - *

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. - * - *

If none of the supplied tasks faulted but at least one of them was cancelled, the returned - * task will end as cancelled. - * - *

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<TResult>} where {@code list.get(0) == t1.getResult(), list.get(1) == t2.getResult(), - * and list.get(2) == t3.getResult()}). - * - *

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<TResult>} 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<TResult>} when all the tasks are - * resolved. - */ - public static Task> whenAllResult( - final Collection> tasks) { - return whenAll(tasks) - .onSuccess( - new Continuation>() { - @Override - public List then(Task task) throws Exception { - if (tasks.size() == 0) { - return Collections.emptyList(); - } - - List results = new ArrayList<>(); - for (Task individualTask : tasks) { - results.add(individualTask.getResult()); - } - return results; - } - }); - } - - /** - * Creates a task that completes when all of the provided tasks are complete. - * - *

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. - * - *

If none of the supplied tasks faulted but at least one of them was cancelled, the returned - * task will end as cancelled. - * - *

If none of the tasks faulted and none of the tasks were canceled, the resulting task will - * end in the completed state. - * - *

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 whenAll(Collection> tasks) { - if (tasks.size() == 0) { - return Task.forResult(null); - } - - final TaskCompletionSource allFinished = new TaskCompletionSource<>(); - final ArrayList 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 t = (Task) task; - t.continueWith( - new Continuation() { - @Override - public Void then(Task 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 continueWhile( - Callable predicate, Continuation> 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 continueWhile( - Callable predicate, - Continuation> 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 continueWhile( - final Callable predicate, - final Continuation> 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 continueWhile( - final Callable predicate, - final Continuation> continuation, - final Executor executor, - final CancellationToken ct) { - final Capture>> predicateContinuation = new Capture<>(); - predicateContinuation.set( - new Continuation>() { - @Override - public Task then(Task task) throws Exception { - if (ct != null && ct.isCancellationRequested()) { - return Task.cancelled(); - } - - if (predicate.call()) { - return Task.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 implements TaskInterface { */ public Task continueWith( final Continuation 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 Task continueWith( - final Continuation continuation, - final Executor executor, - final CancellationToken ct) { boolean completed; final TaskCompletionSource tcs = new TaskCompletionSource<>(); synchronized (lock) { @@ -663,14 +278,14 @@ public class Task implements TaskInterface { new Continuation() { @Override public Void then(Task 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 implements TaskInterface { */ public Task continueWith( Continuation 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 Task continueWith( - Continuation continuation, CancellationToken ct) { - return continueWith(continuation, IMMEDIATE_EXECUTOR, ct); + return continueWith(continuation, IMMEDIATE_EXECUTOR); } /** @@ -700,17 +306,6 @@ public class Task implements TaskInterface { public Task continueWithTask( final Continuation> 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 Task continueWithTask( - final Continuation> continuation, - final Executor executor, - final CancellationToken ct) { boolean completed; final TaskCompletionSource tcs = new TaskCompletionSource<>(); synchronized (lock) { @@ -720,14 +315,14 @@ public class Task implements TaskInterface { new Continuation() { @Override public Void then(Task 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 implements TaskInterface { */ public Task continueWithTask( Continuation> 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 Task continueWithTask( - Continuation> continuation, CancellationToken ct) { - return continueWithTask(continuation, IMMEDIATE_EXECUTOR, ct); + return continueWithTask(continuation, IMMEDIATE_EXECUTOR); } /** @@ -756,25 +342,10 @@ public class Task implements TaskInterface { */ public Task onSuccess( final Continuation 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 Task onSuccess( - final Continuation continuation, - Executor executor, - final CancellationToken ct) { return continueWithTask( new Continuation>() { @Override public Task then(Task 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 implements TaskInterface { */ public Task onSuccess( final Continuation 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 Task onSuccess( - final Continuation continuation, CancellationToken ct) { - return onSuccess(continuation, IMMEDIATE_EXECUTOR, ct); + return onSuccess(continuation, IMMEDIATE_EXECUTOR); } /** @@ -811,25 +373,10 @@ public class Task implements TaskInterface { */ public Task onSuccessTask( final Continuation> 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 Task onSuccessTask( - final Continuation> continuation, - Executor executor, - final CancellationToken ct) { return continueWithTask( new Continuation>() { @Override public Task then(Task 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 implements TaskInterface { return onSuccessTask(continuation, IMMEDIATE_EXECUTOR); } - /** - * Runs a continuation when a task completes successfully, forwarding along {@link - * java.lang.Exception}s or cancellation. - */ - public Task onSuccessTask( - final Continuation> 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 implements TaskInterface { final TaskCompletionSource tcs, final Continuation continuation, final Task 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 implements TaskInterface { final TaskCompletionSource tcs, final Continuation> continuation, final Task 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 result = continuation.then(task); if (result == null) { @@ -939,11 +465,6 @@ public class Task implements TaskInterface { new Continuation() { @Override public Void then(Task task) { - if (ct != null && ct.isCancellationRequested()) { - tcs.setCancelled(); - return null; - } - if (task.isCancelled()) { tcs.setCancelled(); } else if (task.isFaulted()) {