Merge "Use custom background dispatcher to stop excess thread creation." into main
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 461f963..c09bf3e 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -70,7 +70,7 @@
private val taskAnimationManager: TaskAnimationManager,
private val dispatcherProvider: DispatcherProvider = ProductionDispatchers,
) {
- private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.default)
+ private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.background)
private val commandQueue = ConcurrentLinkedDeque<CommandInfo>()
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index b6cb984..e5bad67 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -61,12 +61,14 @@
override val dimProgress: Flow<Float> =
combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
- taskMenuOpenProgress,
- tintAmount ->
- max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
- }
+ taskMenuOpenProgress,
+ tintAmount ->
+ max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
+ }
+ .flowOn(dispatcherProvider.background)
- override val splashAlpha = splashProgress.flatMapLatest { it }
+ override val splashAlpha =
+ splashProgress.flatMapLatest { it }.flowOn(dispatcherProvider.background)
private val isLiveTile =
combine(
@@ -77,7 +79,6 @@
runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
}
.distinctUntilChanged()
- .flowOn(dispatcherProvider.default)
override val uiState: Flow<TaskThumbnailUiState> =
combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
@@ -99,7 +100,7 @@
}
}
.distinctUntilChanged()
- .flowOn(dispatcherProvider.default)
+ .flowOn(dispatcherProvider.background)
override fun bind(taskId: Int) {
Log.d(TAG, "bind taskId: $taskId")
diff --git a/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt b/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
index e9691a8..8877535 100644
--- a/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
+++ b/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
@@ -17,18 +17,44 @@
package com.android.launcher3.util.coroutines
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.newFixedThreadPoolContext
interface DispatcherProvider {
val default: CoroutineDispatcher
- val io: CoroutineDispatcher
+ val background: CoroutineDispatcher
val main: CoroutineDispatcher
val unconfined: CoroutineDispatcher
}
object ProductionDispatchers : DispatcherProvider {
+ private val bgDispatcher = CoroutinesHelper.bgDispatcher()
+
override val default: CoroutineDispatcher = Dispatchers.Default
- override val io: CoroutineDispatcher = Dispatchers.IO
+ override val background: CoroutineDispatcher = bgDispatcher
override val main: CoroutineDispatcher = Dispatchers.Main
override val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
}
+
+private object CoroutinesHelper {
+ /**
+ * Default Coroutine dispatcher for background operations.
+ *
+ * Note that this is explicitly limiting the number of threads. In the past, we used
+ * [Dispatchers.IO]. This caused >40 threads to be spawned, and a lot of thread list lock
+ * contention between then, eventually causing jank.
+ */
+ @OptIn(DelicateCoroutinesApi::class)
+ fun bgDispatcher(): CoroutineDispatcher {
+ // Why a new ThreadPool instead of just using Dispatchers.IO with
+ // CoroutineDispatcher.limitedParallelism? Because, if we were to use Dispatchers.IO, we
+ // would share those threads with other dependencies using Dispatchers.IO.
+ // Using a dedicated thread pool we have guarantees only Launcher is able to schedule
+ // code on those.
+ return newFixedThreadPoolContext(
+ nThreads = Runtime.getRuntime().availableProcessors(),
+ name = "LauncherBg",
+ )
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt b/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt
index 39e1ec5..3319c53 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt
@@ -21,7 +21,7 @@
class TestDispatcherProvider(testDispatcher: CoroutineDispatcher) : DispatcherProvider {
override val default: CoroutineDispatcher = testDispatcher
- override val io: CoroutineDispatcher = testDispatcher
+ override val background: CoroutineDispatcher = testDispatcher
override val main: CoroutineDispatcher = testDispatcher
override val unconfined: CoroutineDispatcher = testDispatcher
}