From 166347ead93ac1128d7519d5211eb32e53659c50 Mon Sep 17 00:00:00 2001
From: Parsa Nasirimehr
Date: Thu, 16 Jan 2025 07:34:03 -0800
Subject: [PATCH] chore(Android): migrate MessageQueueThread and it's
implementation to Kotlin (#48652)
Summary:
Continuing our usual journey, this time migrating MessageQueueThread. Was not expecting to see that many assertions in ReactContext.
One important thing to note on this PR: I had to add an extra Throw RuntimeException to `startNewBackgroundThread`. It already had one from the`dataFuture.getOrThrow()`, but if your thread is not associated with a Looper, calling `myLooper` (Line 203 of MessageQueueThreadImpl) Can return null. Until now, this would have been a `NPE`, i just decided to make it a `RuntimeException` with the message `Looper not found for thread`. Let me know if you want me to make that function return a nullable, or throw another message or exception, or if you want me to treat Looper as Nullable in the whole class.
## Changelog:
[INTERNAL] [FIXED] - Migrate MessageQueueThread and MessageQueueThreadImpl to Kotlin
Pull Request resolved: https://github.com/facebook/react-native/pull/48652
Test Plan:
Reviewed By: tdn120
Differential Revision: D68155120
Pulled By: rshest
fbshipit-source-id: 1082a832df5b1d8ee64ca7be26e4b85e79152f88
---
.../ReactAndroid/api/ReactAndroid.api | 14 +-
...QueueThread.java => MessageQueueThread.kt} | 55 ++---
.../bridge/queue/MessageQueueThreadImpl.java | 226 ------------------
.../bridge/queue/MessageQueueThreadImpl.kt | 188 +++++++++++++++
.../common/futures/SimpleSettableFuture.kt | 2 +-
5 files changed, 223 insertions(+), 262 deletions(-)
rename packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/{MessageQueueThread.java => MessageQueueThread.kt} (51%)
delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java
create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.kt
diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api
index a62c25e8c2b..9a414995c9c 100644
--- a/packages/react-native/ReactAndroid/api/ReactAndroid.api
+++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api
@@ -1664,13 +1664,15 @@ public class com/facebook/react/bridge/queue/MessageQueueThreadHandler : android
public fun dispatchMessage (Landroid/os/Message;)V
}
-public class com/facebook/react/bridge/queue/MessageQueueThreadImpl : com/facebook/react/bridge/queue/MessageQueueThread {
+public final class com/facebook/react/bridge/queue/MessageQueueThreadImpl : com/facebook/react/bridge/queue/MessageQueueThread {
+ public static final field Companion Lcom/facebook/react/bridge/queue/MessageQueueThreadImpl$Companion;
+ public synthetic fun (Ljava/lang/String;Landroid/os/Looper;Lcom/facebook/react/bridge/queue/QueueThreadExceptionHandler;Lcom/facebook/react/bridge/queue/MessageQueueThreadPerfStats;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun assertIsOnThread ()V
public fun assertIsOnThread (Ljava/lang/String;)V
public fun callOnQueue (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future;
- public static fun create (Lcom/facebook/react/bridge/queue/MessageQueueThreadSpec;Lcom/facebook/react/bridge/queue/QueueThreadExceptionHandler;)Lcom/facebook/react/bridge/queue/MessageQueueThreadImpl;
- public fun getLooper ()Landroid/os/Looper;
- public fun getName ()Ljava/lang/String;
+ public static final fun create (Lcom/facebook/react/bridge/queue/MessageQueueThreadSpec;Lcom/facebook/react/bridge/queue/QueueThreadExceptionHandler;)Lcom/facebook/react/bridge/queue/MessageQueueThreadImpl;
+ public final fun getLooper ()Landroid/os/Looper;
+ public final fun getName ()Ljava/lang/String;
public fun getPerfStats ()Lcom/facebook/react/bridge/queue/MessageQueueThreadPerfStats;
public fun isIdle ()Z
public fun isOnThread ()Z
@@ -1679,6 +1681,10 @@ public class com/facebook/react/bridge/queue/MessageQueueThreadImpl : com/facebo
public fun runOnQueue (Ljava/lang/Runnable;)Z
}
+public final class com/facebook/react/bridge/queue/MessageQueueThreadImpl$Companion {
+ public final fun create (Lcom/facebook/react/bridge/queue/MessageQueueThreadSpec;Lcom/facebook/react/bridge/queue/QueueThreadExceptionHandler;)Lcom/facebook/react/bridge/queue/MessageQueueThreadImpl;
+}
+
public class com/facebook/react/bridge/queue/MessageQueueThreadPerfStats {
public field cpuTime J
public field wallTime J
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.kt
similarity index 51%
rename from packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java
rename to packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.kt
index 5656ad58c49..b2d7d937156 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.kt
@@ -5,74 +5,67 @@
* LICENSE file in the root directory of this source tree.
*/
-package com.facebook.react.bridge.queue;
+package com.facebook.react.bridge.queue
-import androidx.annotation.Nullable;
-import com.facebook.proguard.annotations.DoNotStrip;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
+import com.facebook.proguard.annotations.DoNotStripAny
+import com.facebook.react.bridge.AssertionException
+import java.util.concurrent.Callable
+import java.util.concurrent.Future
/** Encapsulates a Thread that can accept Runnables. */
-@DoNotStrip
+@DoNotStripAny
public interface MessageQueueThread {
/**
* Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
* if it is being submitted from the same queue Thread.
*/
- @DoNotStrip
- boolean runOnQueue(Runnable runnable);
+ public fun runOnQueue(runnable: Runnable): Boolean
/**
* Runs the given Callable on this Thread. It will be submitted to the end of the event queue even
* if it is being submitted from the same queue Thread.
*/
- @DoNotStrip
- Future callOnQueue(final Callable callable);
+ public fun callOnQueue(callable: Callable): Future
/**
- * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
+ * Tells whether the current Thread is also the Thread associated with this MessageQueueThread.
*/
- @DoNotStrip
- boolean isOnThread();
+ public fun isOnThread(): Boolean
/**
- * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an {@link
- * AssertionError}) if the assertion fails.
+ * Asserts [isOnThread], throwing a [AssertionException] (NOT an [AssertionError]) if the
+ * assertion fails.
*/
- @DoNotStrip
- void assertIsOnThread();
+ public fun assertIsOnThread()
/**
- * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an {@link
- * AssertionError}) if the assertion fails. The given message is appended to the error.
+ * Asserts [isOnThread], throwing a [AssertionException] (NOT an [AssertionError]) if the
+ * assertion fails. The given message is appended to the error.
*/
- @DoNotStrip
- void assertIsOnThread(String message);
+ public fun assertIsOnThread(message: String)
/**
* Quits this MessageQueueThread. If called from this MessageQueueThread, this will be the last
* thing the thread runs. If called from a separate thread, this will block until the thread can
* be quit and joined.
*/
- @DoNotStrip
- void quitSynchronous();
+ public fun quitSynchronous()
/**
* Returns the perf counters taken when the framework was started. This method is intended to be
* used for instrumentation purposes.
*/
- @Nullable
- @DoNotStrip
- MessageQueueThreadPerfStats getPerfStats();
+ public fun getPerfStats(): MessageQueueThreadPerfStats?
/**
* Resets the perf counters. This is useful if the RN threads are being re-used. This method is
* intended to be used for instrumentation purposes.
*/
- @DoNotStrip
- void resetPerfStats();
+ public fun resetPerfStats()
- /** Returns true if the message queue is idle */
- @DoNotStrip
- boolean isIdle();
+ /**
+ * Resets the perf counters. This is useful if the RN threads are being re-used. This method is
+ * intended to be used for instrumentation purposes.
+ */
+ public fun isIdle(): Boolean
}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java
deleted file mode 100644
index b7e39566ac5..00000000000
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java
+++ /dev/null
@@ -1,226 +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.bridge.queue;
-
-import android.os.Looper;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Pair;
-import androidx.annotation.Nullable;
-import com.facebook.common.logging.FLog;
-import com.facebook.proguard.annotations.DoNotStrip;
-import com.facebook.react.bridge.AssertionException;
-import com.facebook.react.bridge.SoftAssertions;
-import com.facebook.react.common.ReactConstants;
-import com.facebook.react.common.futures.SimpleSettableFuture;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
-
-/** Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables. */
-@DoNotStrip
-public class MessageQueueThreadImpl implements MessageQueueThread {
-
- private final String mName;
- private final Looper mLooper;
- private final MessageQueueThreadHandler mHandler;
- private final String mAssertionErrorMessage;
- private final @Nullable MessageQueueThreadPerfStats mPerfStats;
- private volatile boolean mIsFinished = false;
-
- private MessageQueueThreadImpl(
- String name, Looper looper, QueueThreadExceptionHandler exceptionHandler) {
- this(name, looper, exceptionHandler, null);
- }
-
- private MessageQueueThreadImpl(
- String name,
- Looper looper,
- QueueThreadExceptionHandler exceptionHandler,
- @Nullable MessageQueueThreadPerfStats stats) {
- mName = name;
- mLooper = looper;
- mHandler = new MessageQueueThreadHandler(looper, exceptionHandler);
- mPerfStats = stats;
- mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!";
- }
-
- /**
- * Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
- * if it is being submitted from the same queue Thread.
- */
- @DoNotStrip
- @Override
- public boolean runOnQueue(Runnable runnable) {
- if (mIsFinished) {
- FLog.w(
- ReactConstants.TAG,
- "Tried to enqueue runnable on already finished thread: '"
- + getName()
- + "... dropping Runnable.");
- return false;
- }
- mHandler.post(runnable);
- return true;
- }
-
- @DoNotStrip
- @Override
- public Future callOnQueue(final Callable callable) {
- final SimpleSettableFuture future = new SimpleSettableFuture<>();
- runOnQueue(
- () -> {
- try {
- future.set(callable.call());
- } catch (Exception e) {
- future.setException(e);
- }
- });
- return future;
- }
-
- /**
- * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
- */
- @DoNotStrip
- @Override
- public boolean isOnThread() {
- return mLooper.getThread() == Thread.currentThread();
- }
-
- /**
- * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an {@link
- * AssertionError}) if the assertion fails.
- */
- @DoNotStrip
- @Override
- public void assertIsOnThread() {
- SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage);
- }
-
- /**
- * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an {@link
- * AssertionError}) if the assertion fails.
- */
- @DoNotStrip
- @Override
- public void assertIsOnThread(String message) {
- SoftAssertions.assertCondition(
- isOnThread(),
- new StringBuilder().append(mAssertionErrorMessage).append(" ").append(message).toString());
- }
-
- /**
- * Quits this queue's Looper. If that Looper was running on a different Thread than the current
- * Thread, also waits for the last message being processed to finish and the Thread to die.
- */
- @DoNotStrip
- @Override
- public void quitSynchronous() {
- mIsFinished = true;
- mLooper.quit();
- if (mLooper.getThread() != Thread.currentThread()) {
- try {
- mLooper.getThread().join();
- } catch (InterruptedException e) {
- throw new RuntimeException("Got interrupted waiting to join thread " + mName);
- }
- }
- }
-
- @Nullable
- @DoNotStrip
- @Override
- public MessageQueueThreadPerfStats getPerfStats() {
- return mPerfStats;
- }
-
- @DoNotStrip
- @Override
- public void resetPerfStats() {
- assignToPerfStats(mPerfStats, -1, -1);
- runOnQueue(
- () -> {
- long wallTime = SystemClock.uptimeMillis();
- long cpuTime = SystemClock.currentThreadTimeMillis();
- assignToPerfStats(mPerfStats, wallTime, cpuTime);
- });
- }
-
- @DoNotStrip
- @Override
- public boolean isIdle() {
- return mLooper.getQueue().isIdle();
- }
-
- private static void assignToPerfStats(
- @Nullable MessageQueueThreadPerfStats stats, long wall, long cpu) {
- if (stats == null) {
- return;
- }
- stats.wallTime = wall;
- stats.cpuTime = cpu;
- }
-
- public Looper getLooper() {
- return mLooper;
- }
-
- public String getName() {
- return mName;
- }
-
- public static MessageQueueThreadImpl create(
- MessageQueueThreadSpec spec, QueueThreadExceptionHandler exceptionHandler) {
- switch (spec.getThreadType()) {
- case MAIN_UI:
- return createForMainThread(spec.getName(), exceptionHandler);
- case NEW_BACKGROUND:
- return startNewBackgroundThread(spec.getName(), spec.getStackSize(), exceptionHandler);
- default:
- throw new RuntimeException("Unknown thread type: " + spec.getThreadType());
- }
- }
-
- /**
- * @return a MessageQueueThreadImpl corresponding to Android's main UI thread.
- */
- private static MessageQueueThreadImpl createForMainThread(
- String name, QueueThreadExceptionHandler exceptionHandler) {
- return new MessageQueueThreadImpl(name, Looper.getMainLooper(), exceptionHandler);
- }
-
- /**
- * Creates and starts a new MessageQueueThreadImpl encapsulating a new Thread with a new Looper
- * running on it. Give it a name for easier debugging and optionally a suggested stack size. When
- * this method exits, the new MessageQueueThreadImpl is ready to receive events.
- */
- private static MessageQueueThreadImpl startNewBackgroundThread(
- final String name, long stackSize, QueueThreadExceptionHandler exceptionHandler) {
- final SimpleSettableFuture> dataFuture =
- new SimpleSettableFuture<>();
- Thread bgThread =
- new Thread(
- null,
- () -> {
- Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
- Looper.prepare();
- MessageQueueThreadPerfStats stats = new MessageQueueThreadPerfStats();
- long wallTime = SystemClock.uptimeMillis();
- long cpuTime = SystemClock.currentThreadTimeMillis();
- assignToPerfStats(stats, wallTime, cpuTime);
- dataFuture.set(new Pair<>(Looper.myLooper(), stats));
- Looper.loop();
- },
- "mqt_" + name,
- stackSize);
- bgThread.start();
-
- Pair pair = dataFuture.getOrThrow();
- return new MessageQueueThreadImpl(name, pair.first, exceptionHandler, pair.second);
- }
-}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.kt
new file mode 100644
index 00000000000..d16071ac032
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.bridge.queue
+
+import android.os.Looper
+import android.os.Process
+import android.os.SystemClock
+import android.util.Pair
+import com.facebook.common.logging.FLog
+import com.facebook.proguard.annotations.DoNotStripAny
+import com.facebook.react.bridge.AssertionException
+import com.facebook.react.bridge.SoftAssertions
+import com.facebook.react.bridge.queue.MessageQueueThreadSpec.ThreadType
+import com.facebook.react.common.ReactConstants
+import com.facebook.react.common.futures.SimpleSettableFuture
+import java.util.concurrent.Callable
+import java.util.concurrent.Future
+import kotlin.concurrent.Volatile
+
+/** Encapsulates a Thread that has a [Looper] running on it that can accept Runnables. */
+@DoNotStripAny
+public class MessageQueueThreadImpl
+private constructor(
+ public val name: String,
+ public val looper: Looper,
+ exceptionHandler: QueueThreadExceptionHandler,
+ private val stats: MessageQueueThreadPerfStats? = null
+) : MessageQueueThread {
+ private val handler = MessageQueueThreadHandler(looper, exceptionHandler)
+ private val assertionErrorMessage = "Expected to be called from the '$name' thread!"
+
+ @Volatile private var isFinished = false
+
+ /**
+ * Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
+ * if it is being submitted from the same queue Thread.
+ */
+ public override fun runOnQueue(runnable: Runnable): Boolean {
+ if (isFinished) {
+ FLog.w(
+ ReactConstants.TAG,
+ "Tried to enqueue runnable on already finished thread: '$name... dropping Runnable.")
+ return false
+ }
+ handler.post(runnable)
+ return true
+ }
+
+ public override fun callOnQueue(callable: Callable): Future {
+ val future = SimpleSettableFuture()
+ runOnQueue {
+ try {
+ future.set(callable.call())
+ } catch (e: Exception) {
+ future.setException(e)
+ }
+ }
+ return future
+ }
+
+ /**
+ * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
+ */
+ override fun isOnThread(): Boolean = looper.thread === Thread.currentThread()
+
+ /**
+ * Asserts [isOnThread], throwing a [AssertionException] (NOT an [AssertionError]) if the
+ * assertion fails.
+ */
+ @Throws(AssertionException::class)
+ override fun assertIsOnThread() {
+ SoftAssertions.assertCondition(isOnThread(), assertionErrorMessage)
+ }
+
+ /**
+ * Asserts [isOnThread], throwing a [AssertionException] (NOT an [AssertionError]) if the
+ * assertion fails.
+ */
+ @Throws(AssertionException::class)
+ public override fun assertIsOnThread(message: String) {
+ SoftAssertions.assertCondition(
+ isOnThread(),
+ StringBuilder().append(assertionErrorMessage).append(" ").append(message).toString())
+ }
+
+ /**
+ * Quits this queue's Looper. If that Looper was running on a different Thread than the current
+ * Thread, also waits for the last message being processed to finish and the Thread to die.
+ */
+ @Throws(RuntimeException::class)
+ override fun quitSynchronous() {
+ isFinished = true
+ looper.quit()
+ if (looper.thread !== Thread.currentThread()) {
+ try {
+ looper.thread.join()
+ } catch (e: InterruptedException) {
+ throw RuntimeException("Got interrupted waiting to join thread $name")
+ }
+ }
+ }
+
+ override fun getPerfStats(): MessageQueueThreadPerfStats? = stats
+
+ override fun resetPerfStats() {
+ assignToPerfStats(stats, -1, -1)
+ runOnQueue {
+ val wallTime = SystemClock.uptimeMillis()
+ val cpuTime = SystemClock.currentThreadTimeMillis()
+ assignToPerfStats(stats, wallTime, cpuTime)
+ }
+ }
+
+ public override fun isIdle(): Boolean = looper.queue.isIdle
+
+ public companion object {
+ private fun assignToPerfStats(stats: MessageQueueThreadPerfStats?, wall: Long, cpu: Long) {
+ stats?.let { s ->
+ s.wallTime = wall
+ s.cpuTime = cpu
+ }
+ }
+
+ @JvmStatic
+ @Throws(RuntimeException::class)
+ public fun create(
+ spec: MessageQueueThreadSpec,
+ exceptionHandler: QueueThreadExceptionHandler
+ ): MessageQueueThreadImpl {
+ return when (spec.threadType) {
+ ThreadType.MAIN_UI -> createForMainThread(spec.name, exceptionHandler)
+
+ ThreadType.NEW_BACKGROUND ->
+ startNewBackgroundThread(spec.name, spec.stackSize, exceptionHandler)
+
+ else -> throw RuntimeException("Unknown thread type: " + spec.threadType)
+ }
+ }
+
+ /** Returns a MessageQueueThreadImpl corresponding to Android's main UI thread. */
+ private fun createForMainThread(
+ name: String,
+ exceptionHandler: QueueThreadExceptionHandler
+ ): MessageQueueThreadImpl =
+ MessageQueueThreadImpl(name, Looper.getMainLooper(), exceptionHandler)
+
+ /**
+ * Creates and starts a new MessageQueueThreadImpl encapsulating a new Thread with a new Looper
+ * running on it. Give it a name for easier debugging and optionally a suggested stack size.
+ * When this method exits, the new MessageQueueThreadImpl is ready to receive events. throws a
+ * Runtime exception if there was no looper for current thread or looper and stats couldn't be
+ * made
+ */
+ @Throws(RuntimeException::class)
+ private fun startNewBackgroundThread(
+ name: String,
+ stackSize: Long,
+ exceptionHandler: QueueThreadExceptionHandler
+ ): MessageQueueThreadImpl {
+ val dataFuture = SimpleSettableFuture>()
+ val bgThread =
+ Thread(
+ null,
+ {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY)
+ Looper.prepare()
+ val stats = MessageQueueThreadPerfStats()
+ val wallTime = SystemClock.uptimeMillis()
+ val cpuTime = SystemClock.currentThreadTimeMillis()
+ assignToPerfStats(stats, wallTime, cpuTime)
+ dataFuture.set(Pair(Looper.myLooper(), stats))
+ Looper.loop()
+ },
+ "mqt_$name",
+ stackSize)
+ bgThread.start()
+
+ val pair = dataFuture.getOrThrow()
+ val looper = pair?.first ?: throw RuntimeException("Looper not found for thread")
+ return MessageQueueThreadImpl(name, looper, exceptionHandler, pair.second)
+ }
+ }
+}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.kt
index 95c093e6670..3376f4d2a2d 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.kt
@@ -17,7 +17,7 @@ import java.util.concurrent.TimeoutException
* A super simple Future-like class that can safely notify another Thread when a value is ready.
* Does not support canceling.
*/
-internal class SimpleSettableFuture : Future {
+internal class SimpleSettableFuture : Future {
private val readyLatch = CountDownLatch(1)
private var result: T? = null