Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 59bc0cda authored by Kshitij Gupta's avatar Kshitij Gupta Committed by Android (Google) Code Review
Browse files

Merge "SystemUI: Implement IntrinsicLockDispatcher for background tasks" into main

parents 1a7d5064 e49c690e
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -17,12 +17,14 @@
package com.android.systemui.util.kotlin

import android.os.Handler
import com.android.systemui.Flags
import com.android.systemui.coroutines.newTracingContext
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.NotifInflation
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.util.kotlin.dispatchers.newIntrinsicLockFixedThreadPoolContext
import com.android.systemui.util.settings.SettingsSingleThreadBackground
import dagger.Module
import dagger.Provides
@@ -72,10 +74,17 @@ class SysUICoroutinesModule {
            // 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.
            if (Flags.sysuiIntrinsicLockDispatcher()) {
                newIntrinsicLockFixedThreadPoolContext(
                    nThreads = Runtime.getRuntime().availableProcessors(),
                    name ="SystemUIBg",
                )
            } else {
                newFixedThreadPoolContext(
                    nThreads = Runtime.getRuntime().availableProcessors(),
                    name = "SystemUIBg",
                )
            }
        } else {
            Dispatchers.IO
        }
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.util.kotlin.dispatchers

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DelicateCoroutinesApi
import java.io.Closeable
import java.util.concurrent.ExecutorService
import java.util.concurrent.ThreadFactory
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext

/**
 * A coroutine dispatcher backed by a ThreadPoolExecutor that uses a custom,
 * ReentrantLock-free BlockingQueue.
 *
 * This serves as a drop-in replacement for `newFixedThreadPoolContext` to avoid
 * the main-thread stalls caused by lock contention in the standard implementation.
 *
 * @param corePoolSize The number of threads to keep in the pool, even if they are idle.
 * @param maxPoolSize The maximum number of threads to allow in the pool.
 * @param keepAliveTime When the number of threads is greater than the core, this is the maximum
 * time that excess idle threads will wait for new tasks before terminating.
 * @param unit The time unit for the keepAliveTime argument.
 * @param dispatcherName A base name for the dispatcher's threads, useful for debugging.
 */
class IntrinsicLockDispatcher(
    corePoolSize: Int,
    maxPoolSize: Int,
    keepAliveTime: Long = 10L,
    unit: TimeUnit = TimeUnit.MILLISECONDS,
    dispatcherName: String = "IntrinsicLockDispatcherPool"
) : CoroutineDispatcher(), Closeable {

    private val executor: ExecutorService

    companion object {
        /**
         * Helper function to create a [ThreadFactory] with a custom name prefix for
         * easier debugging.
         */
        private fun threadFactory(namePrefix: String) = object : ThreadFactory {
            private val count = AtomicInteger(0)
            override fun newThread(r: Runnable): Thread {
                return Thread(r, "$namePrefix-${count.incrementAndGet()}").also { it.isDaemon = true }
            }
        }
    }

    init {
        executor = ThreadPoolExecutor(
            corePoolSize,
            maxPoolSize,
            keepAliveTime,
            unit,
            SynchronizedLinkedBlockingQueue(),
            threadFactory(dispatcherName)
        )
    }
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        executor.execute(block)
    }

    override fun close() {
        executor.shutdown()
    }
}

/**
 * Creates a new CoroutineDispatcher with a fixed-size thread pool that is free
 * of `ReentrantLock` to avoid specific main-thread contention issues.
 */
@DelicateCoroutinesApi
fun newIntrinsicLockFixedThreadPoolContext(nThreads: Int, name: String): CoroutineDispatcher {
    require(nThreads >= 1) { "Expected at least one thread, but $nThreads specified" }
    return IntrinsicLockDispatcher(
        corePoolSize = nThreads,
        maxPoolSize = nThreads,
        dispatcherName = name
    )
}
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.util.kotlin.dispatchers

import java.util.AbstractQueue
import java.util.ArrayDeque
import java.util.concurrent.BlockingQueue
import java.util.concurrent.TimeUnit

/**
 * A custom implementation of a blocking queue that uses a simple LinkedList
 * and intrinsic monitor locks (`synchronized`) instead of `ReentrantLock`.
 * This avoids the specific contention issues that can arise from ReentrantLock
 * in scenarios like the one described in the Perfetto case study.
 */
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
internal class SynchronizedLinkedBlockingQueue : AbstractQueue<Runnable>(), BlockingQueue<Runnable> {

    private val queue = ArrayDeque<Runnable>()

    override val size: Int
        @Synchronized get() = queue.size

    @Synchronized
    override fun offer(e: Runnable): Boolean {
        // This queue is unbounded, so it always succeeds.
        queue.addLast(e)
        (this as Object).notifyAll()
        return true
    }

    @Synchronized
    override fun offer(e: Runnable, timeout: Long, unit: TimeUnit): Boolean {
        // Unbounded queue, timeout is irrelevant.
        return offer(e)
    }

    @Synchronized
    override fun put(e: Runnable) {
        queue.addLast(e)
        (this as Object).notifyAll()
    }

    @Synchronized
    override fun take(): Runnable {
        while (queue.isEmpty()) {
            (this as Object).wait()
        }
        return queue.removeFirst()
    }

    @Synchronized
    override fun poll(timeout: Long, unit: TimeUnit): Runnable? {
        if (queue.isNotEmpty()) {
            return queue.removeFirst()
        }
        val millis = unit.toMillis(timeout)
        // Check for timeout > 0 to avoid waiting forever on wait(0).
        if (millis > 0) {
            (this as Object).wait(millis)
        }
        return queue.pollFirst()
    }

    @Synchronized
    override fun poll(): Runnable? = queue.pollFirst()

    @Synchronized
    override fun peek(): Runnable? = queue.peekFirst()

    // Not required by ThreadPoolExecutor
    override fun iterator(): MutableIterator<Runnable> {
        throw UnsupportedOperationException("iterator() not supported by SynchronizedLinkedBlockingQueue")
    }

    // Not required by ThreadPoolExecutor
    override fun drainTo(c: MutableCollection<in Runnable>): Int {
        throw UnsupportedOperationException("drainTo() not supported by SynchronizedLinkedBlockingQueue")
    }

    // Not required by ThreadPoolExecutor
    override fun drainTo(c: MutableCollection<in Runnable>, maxElements: Int): Int {
        throw UnsupportedOperationException("drainTo() not supported by SynchronizedLinkedBlockingQueue")
    }

    override fun remainingCapacity(): Int {
        return Int.MAX_VALUE // Unbounded
    }
}