• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.coroutines.debug
6 
7 import kotlinx.coroutines.*
8 import org.junit.Test
9 import kotlin.coroutines.*
10 import kotlin.test.*
11 
12 class CoroutinesDumpTest : DebugTestBase() {
13     private val monitor = Any()
14     private var coroutineThread: Thread? = null // guarded by monitor
15 
16     @Test
17     fun testSuspendedCoroutine() = runBlocking {
18         val deferred = async(Dispatchers.Default) {
19             sleepingOuterMethod()
20         }
21 
22         awaitCoroutine()
23         val found = DebugProbes.dumpCoroutinesInfo().single { it.job === deferred }
24         verifyDump(
25             "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: SUSPENDED\n" +
26                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingNestedMethod(CoroutinesDumpTest.kt:95)\n" +
27                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingOuterMethod(CoroutinesDumpTest.kt:88)\n" +
28                     "\t(Coroutine creation stacktrace)\n" +
29                     "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
30                     "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" +
31                     "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n",
32             ignoredCoroutine = "BlockingCoroutine"
33         ) {
34             deferred.cancel()
35             coroutineThread!!.interrupt()
36         }
37         assertSame(deferred, found.job)
38     }
39 
40     @Test
41     fun testRunningCoroutine() = runBlocking {
42         val deferred = async(Dispatchers.IO) {
43             activeMethod(shouldSuspend = false)
44             assertTrue(true)
45         }
46 
47         awaitCoroutine()
48         verifyDump(
49             "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@227d9994, state: RUNNING\n" +
50                     "\tat java.lang.Thread.sleep(Native Method)\n" +
51                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:141)\n" +
52                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:133)\n" +
53                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest\$testRunningCoroutine\$1$deferred\$1.invokeSuspend(CoroutinesDumpTest.kt:41)\n" +
54                     "\t(Coroutine creation stacktrace)\n" +
55                     "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
56                     "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" +
57                     "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" +
58                     "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:148)\n" +
59                     "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt)\n" +
60                     "\tat kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" +
61                     "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" +
62                     "\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
63                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.testRunningCoroutine(CoroutinesDumpTest.kt:49)",
64             ignoredCoroutine = "BlockingCoroutine"
65         ) {
66             deferred.cancel()
67             coroutineThread?.interrupt()
68         }
69     }
70 
71     @Test
72     fun testRunningCoroutineWithSuspensionPoint() = runBlocking {
73         val deferred = async(Dispatchers.IO) {
74             activeMethod(shouldSuspend = true)
75             yield() // tail-call
76         }
77 
78         awaitCoroutine()
79         verifyDump(
80             "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: RUNNING\n" +
81                     "\tat java.lang.Thread.sleep(Native Method)\n" +
82                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:111)\n" +
83                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:106)\n" +
84                     "\t(Coroutine creation stacktrace)\n" +
85                     "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" +
86                     "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" +
87                     "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" +
88                     "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:148)\n" +
89                     "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt)\n" +
90                     "\tat kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" +
91                     "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" +
92                     "\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
93                     "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.testRunningCoroutineWithSuspensionPoint(CoroutinesDumpTest.kt:71)",
94             ignoredCoroutine = "BlockingCoroutine"
95         ) {
96             deferred.cancel()
97             coroutineThread!!.interrupt()
98         }
99     }
100 
101     @Test
102     fun testCreationStackTrace() = runBlocking {
103         val deferred = async(Dispatchers.IO) {
104             activeMethod(shouldSuspend = true)
105         }
106 
107         awaitCoroutine()
108         val coroutine = DebugProbes.dumpCoroutinesInfo().first { it.job is Deferred<*> }
109         val result = coroutine.creationStackTrace.fold(StringBuilder()) { acc, element ->
110             acc.append(element.toString())
111             acc.append('\n')
112         }.toString().trimStackTrace()
113 
114         deferred.cancel()
115         coroutineThread!!.interrupt()
116 
117         val expected =
118             "kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt)\n" +
119             "kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt)\n" +
120             "kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable\$default(Cancellable.kt)\n" +
121             "kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt)\n" +
122             "kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt)\n" +
123             "kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt)\n" +
124             "kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" +
125             "kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" +
126             "kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" +
127             "kotlinx.coroutines.debug.CoroutinesDumpTest\$testCreationStackTrace\$1.invokeSuspend(CoroutinesDumpTest.kt)"
128         if (!result.startsWith(expected)) {
129             println("=== Actual result")
130             println(result)
131             error("Does not start with expected lines")
132         }
133 
134     }
135 
136     @Test
137     fun testFinishedCoroutineRemoved() = runBlocking {
138         val deferred = async(Dispatchers.IO) {
139             activeMethod(shouldSuspend = true)
140         }
141 
142         awaitCoroutine()
143         deferred.cancel()
144         coroutineThread!!.interrupt()
145         deferred.join()
146         verifyDump(ignoredCoroutine = "BlockingCoroutine")
147     }
148 
149     private suspend fun activeMethod(shouldSuspend: Boolean) {
150         nestedActiveMethod(shouldSuspend)
151         assertTrue(true) // tail-call
152     }
153 
154     private suspend fun nestedActiveMethod(shouldSuspend: Boolean) {
155         if (shouldSuspend) yield()
156         notifyCoroutineStarted()
157         while (coroutineContext[Job]!!.isActive) {
158             try {
159                 Thread.sleep(60_000)
160             } catch (_ : InterruptedException) {
161             }
162         }
163     }
164 
165     private suspend fun sleepingOuterMethod() {
166         sleepingNestedMethod()
167         yield() // TCE
168     }
169 
170     private suspend fun sleepingNestedMethod() {
171         yield() // Suspension point
172         notifyCoroutineStarted()
173         delay(Long.MAX_VALUE)
174     }
175 
176     private fun awaitCoroutine() = synchronized(monitor) {
177         while (coroutineThread == null) (monitor as Object).wait()
178         while (coroutineThread!!.state != Thread.State.TIMED_WAITING) {
179             // Wait until thread sleeps to have a consistent stacktrace
180         }
181     }
182 
183     private fun notifyCoroutineStarted() {
184         synchronized(monitor) {
185             coroutineThread = Thread.currentThread()
186             (monitor as Object).notifyAll()
187         }
188     }
189 }
190