xref: /aosp_15_r20/external/kotlinx.coroutines/test-utils/jvm/src/Threads.kt (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)

<lambda>null1 package kotlinx.coroutines.testing
2 
3 import kotlinx.coroutines.*
4 import java.lang.Runnable
5 
6 private const val WAIT_LOST_THREADS = 10_000L // 10s
7 private val ignoreLostThreads = mutableSetOf<String>()
8 
9 fun ignoreLostThreads(vararg s: String) { ignoreLostThreads += s }
10 
currentThreadsnull11 fun currentThreads(): Set<Thread> {
12     var estimate = 0
13     while (true) {
14         estimate = estimate.coerceAtLeast(Thread.activeCount() + 1)
15         val arrayOfThreads = Array<Thread?>(estimate) { null }
16         val n = Thread.enumerate(arrayOfThreads)
17         if (n >= estimate) {
18             estimate = n + 1
19             continue // retry with a better size estimate
20         }
21         val threads = hashSetOf<Thread>()
22         for (i in 0 until n)
23             threads.add(arrayOfThreads[i]!!)
24         return threads
25     }
26 }
27 
dumpThreadsnull28 fun List<Thread>.dumpThreads(header: String) {
29     println("=== $header")
30     forEach { thread ->
31         println("Thread \"${thread.name}\" ${thread.state}")
32         val trace = thread.stackTrace
33         for (t in trace) println("\tat ${t.className}.${t.methodName}(${t.fileName}:${t.lineNumber})")
34         println()
35     }
36     println("===")
37 }
38 
39 class PoolThread(
40     @JvmField val dispatcher: ExecutorCoroutineDispatcher, // for debugging & tests
41     target: Runnable, name: String
42 ) : Thread(target, name) {
43     init {
44         isDaemon = true
45     }
46 }
47 
dumpThreadsnull48 fun ExecutorCoroutineDispatcher.dumpThreads(header: String) =
49     currentThreads().filter { it is PoolThread && it.dispatcher == this@dumpThreads }.dumpThreads(header)
50 
checkTestThreadsnull51 fun checkTestThreads(threadsBefore: Set<Thread>) {
52     // give threads some time to shutdown
53     val waitTill = System.currentTimeMillis() + WAIT_LOST_THREADS
54     var diff: List<Thread>
55     do {
56         val threadsAfter = currentThreads()
57         diff = (threadsAfter - threadsBefore).filter { thread ->
58             ignoreLostThreads.none { prefix -> thread.name.startsWith(prefix) }
59         }
60         if (diff.isEmpty()) break
61     } while (System.currentTimeMillis() <= waitTill)
62     ignoreLostThreads.clear()
63     if (diff.isEmpty()) return
64     val message = "Lost threads ${diff.map { it.name }}"
65     println("!!! $message")
66     diff.dumpThreads("Dumping lost thread stack traces")
67     error(message)
68 }
69