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