1 package kotlinx.coroutines.debug
2 
3 import kotlinx.coroutines.testing.*
4 import kotlinx.coroutines.*
5 import kotlinx.coroutines.debug.internal.*
6 import org.junit.Test
7 import java.util.concurrent.*
8 import kotlin.test.*
9 
10 class RunningThreadStackMergeTest : DebugTestBase() {
11 
12     private val testMainBlocker = CountDownLatch(1) // Test body blocks on it
13     private val coroutineBlocker = CyclicBarrier(2) // Launched coroutine blocks on it
14 
15     @Test
<lambda>null16     fun testStackMergeWithContext() = runTest {
17         launchCoroutine()
18         awaitCoroutineStarted()
19         verifyDump(
20             "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@50284dc4, state: RUNNING\n" +
21                 "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" +
22                 "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
23                 "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
24                 "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
25                 "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" +
26                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:86)\n" +
27                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.access\$nonSuspendingFun(RunningThreadStackMergeTest.kt:12)\n" +
28                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunction\$2.invokeSuspend(RunningThreadStackMergeTest.kt:77)\n" +
29                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunction(RunningThreadStackMergeTest.kt:75)\n" +
30                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:68)",
31             ignoredCoroutine = "BlockingCoroutine"
32         ) {
33             coroutineBlocker.await()
34         }
35     }
36 
awaitCoroutineStartednull37     private fun awaitCoroutineStarted() {
38         testMainBlocker.await()
39         while (coroutineBlocker.numberWaiting != 1) {
40             Thread.sleep(10)
41         }
42     }
43 
CoroutineScopenull44     private fun CoroutineScope.launchCoroutine() {
45         launch(Dispatchers.Default) {
46             suspendingFunction()
47             assertTrue(true)
48         }
49     }
50 
suspendingFunctionnull51     private suspend fun suspendingFunction() {
52         // Typical use-case
53         withContext(Dispatchers.IO) {
54             yield()
55             nonSuspendingFun()
56         }
57 
58         assertTrue(true)
59     }
60 
nonSuspendingFunnull61     private fun nonSuspendingFun() {
62         testMainBlocker.countDown()
63         coroutineBlocker.await()
64     }
65 
66     @Test
<lambda>null67     fun testStackMergeEscapeSuspendMethod() = runTest {
68         launchEscapingCoroutine()
69         awaitCoroutineStarted()
70         Thread.sleep(10)
71         verifyDump(
72             "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" +
73                 "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" +
74                 "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
75                 "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
76                 "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
77                 "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" +
78                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:83)\n" +
79                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.access\$nonSuspendingFun(RunningThreadStackMergeTest.kt:12)\n" +
80                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunctionWithContext\$2.invokeSuspend(RunningThreadStackMergeTest.kt:124)\n" +
81                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithContext(RunningThreadStackMergeTest.kt:122)\n" +
82                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:116)",
83             ignoredCoroutine = "BlockingCoroutine"
84         ) {
85             coroutineBlocker.await()
86         }
87     }
88 
CoroutineScopenull89     private fun CoroutineScope.launchEscapingCoroutine() {
90         launch(Dispatchers.Default) {
91             suspendingFunctionWithContext()
92             assertTrue(true)
93         }
94     }
95 
suspendingFunctionWithContextnull96     private suspend fun suspendingFunctionWithContext() {
97         withContext(Dispatchers.IO) {
98             actualSuspensionPoint()
99             nonSuspendingFun()
100         }
101 
102         assertTrue(true)
103     }
104 
105     @Test
<lambda>null106     fun testMergeThroughInvokeSuspend() = runTest {
107         launchEscapingCoroutineWithoutContext()
108         awaitCoroutineStarted()
109         verifyDump(
110             "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" +
111                 "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" +
112                 "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" +
113                 "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" +
114                 "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" +
115                 "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" +
116                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:83)\n" +
117                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithoutContext(RunningThreadStackMergeTest.kt:160)\n" +
118                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutineWithoutContext\$1.invokeSuspend(RunningThreadStackMergeTest.kt:153)",
119             ignoredCoroutine = "BlockingCoroutine"
120         ) {
121             coroutineBlocker.await()
122         }
123     }
124 
CoroutineScopenull125     private fun CoroutineScope.launchEscapingCoroutineWithoutContext() {
126         launch(Dispatchers.IO) {
127             suspendingFunctionWithoutContext()
128             assertTrue(true)
129         }
130     }
131 
suspendingFunctionWithoutContextnull132     private suspend fun suspendingFunctionWithoutContext() {
133         actualSuspensionPoint()
134         nonSuspendingFun()
135         assertTrue(true)
136     }
137 
138     @Test
<lambda>null139     fun testRunBlocking() = runBlocking {
140         verifyDump(
141             "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@4bcd176c, state: RUNNING\n" +
142                 "\tat java.lang.Thread.getStackTrace(Thread.java)\n" +
143                 "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.enhanceStackTraceWithThreadDumpImpl(DebugProbesImpl.kt)\n" +
144                 "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutinesSynchronized(DebugProbesImpl.kt)\n" +
145                 "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutines(DebugProbesImpl.kt)\n" +
146                 "\tat kotlinx.coroutines.debug.DebugProbes.dumpCoroutines(DebugProbes.kt)\n" +
147                 "\tat kotlinx.coroutines.debug.StacktraceUtilsKt.verifyDump(StacktraceUtils.kt)\n" +
148                 "\tat kotlinx.coroutines.debug.StacktraceUtilsKt.verifyDump\$default(StacktraceUtils.kt)\n" +
149                 "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$testRunBlocking\$1.invokeSuspend(RunningThreadStackMergeTest.kt)"
150         )
151     }
152 
153 
actualSuspensionPointnull154     private suspend fun actualSuspensionPoint() {
155         nestedSuspensionPoint()
156         assertTrue(true)
157     }
158 
nestedSuspensionPointnull159     private suspend fun nestedSuspensionPoint() {
160         yield()
161         assertTrue(true)
162     }
163 
164     @Test // IDEA-specific debugger API test
165     @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
<lambda>null166     fun testActiveThread() = runBlocking<Unit> {
167         launchCoroutine()
168         awaitCoroutineStarted()
169         val info = DebugProbesImpl.dumpDebuggerInfo().find { it.state == "RUNNING" }
170         assertNotNull(info)
171         assertNotNull(info.lastObservedThreadName)
172         coroutineBlocker.await()
173     }
174 }
175