From fdfa012dae3a2112c5fcebe300ca359eeae9fefa Mon Sep 17 00:00:00 2001 From: David Vacca Date: Tue, 6 Jun 2023 17:43:26 -0700 Subject: [PATCH] Introduce BoltsFutureTask class to avoid leaking bolts.Task into ReactHost API (#37744) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37744 This diff introduces the new generic class BoltsFutureTask<> that will be used by ReactHost to expose async task in its API. The goal of BoltsFutureTask is to avoid leadking the bolts.Task dependency into the ReactHost API. Public methods of this class follow the spec defined in the Future interface, see: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html. I didn't add javadocs to avoid duplicated documentation with Future and also becuase this is private class. Task Cancellation follows the patters defined in bolts documentation: https://github.com/BoltsFramework/Bolts-Android#cancelling-tasks changelog: [internal] internal Reviewed By: NickGerleman Differential Revision: D46170466 fbshipit-source-id: eed92584bcd3ff0145e7cbb5d735139fbb6b0f33 --- .../react/bridgeless/BoltsFutureTask.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BoltsFutureTask.java diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BoltsFutureTask.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BoltsFutureTask.java new file mode 100644 index 00000000000..a2a7903a854 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/BoltsFutureTask.java @@ -0,0 +1,93 @@ +/* + * 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.bridgeless; + +import com.facebook.react.bridgeless.internal.bolts.CancellationTokenSource; +import com.facebook.react.bridgeless.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(); + } +}