Limit concurrency SysUI bg coroutine dispatcher

This limits the number of threads the systemui bg coroutine dispatcher can use. Before this change, the limit was the max between "kotlinx.coroutines.io.parallelism" and the number of cpus. In sysui we found traces with >40 threads and a lot of thread list lock contention. As the lock contention was also bocking the main thread, it ended up causing jank. The more SysUI switches work to be scheduled in the background, the more jank there would have been.

The value of available processor has been identified comparing unbounded, 5 and 10 bg threads. Available processors seem to provide the best values in terms of lock contention and jank.

Bug: 322437228
Bug: 321027720
Flag: None as this is fixing a recent regression already in the field.
Test: performance tests
Change-Id: I5b160fd69b8f3357d582420ef6713ae8cc2a43aa
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index a13d85b..cabe831 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -25,9 +25,13 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.newFixedThreadPoolContext
 import kotlinx.coroutines.plus
 
+private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
+
 /** Providers for various SystemIU specific coroutines-related constructs. */
 @Module
 class SysUICoroutinesModule {
@@ -40,14 +44,13 @@
     ): CoroutineScope = applicationScope.plus(coroutineContext)
 
     /**
-     * Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where X
-     * is the number of CPU cores available.
+     * Default Coroutine dispatcher for background operations.
      *
-     * Because there are multiple threads at play, there is no serialization order guarantee. You
-     * should use a [kotlinx.coroutines.channels.Channel] for serialization if necessary.
-     *
-     * @see Dispatchers.Default
+     * 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)
     @Provides
     @SysUISingleton
     @Background
@@ -55,12 +58,29 @@
         "Use @Background CoroutineContext instead",
         ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
     )
-    fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+    fun bgDispatcher(): CoroutineDispatcher {
+        return if (LIMIT_BACKGROUND_DISPATCHER_THREADS) {
+            // 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 SystemUI is able to schedule
+            // code on those.
+            newFixedThreadPoolContext(
+                nThreads = Runtime.getRuntime().availableProcessors(),
+                name = "SystemUIBg"
+            )
+        } else {
+            Dispatchers.IO
+        }
+    }
 
     @Provides
     @Background
     @SysUISingleton
-    fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
-        return Dispatchers.IO + tracingCoroutineContext
+    fun bgCoroutineContext(
+        @Tracing tracingCoroutineContext: CoroutineContext,
+        @Background bgCoroutineDispatcher: CoroutineDispatcher,
+    ): CoroutineContext {
+        return bgCoroutineDispatcher + tracingCoroutineContext
     }
 }