-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Attempt to fix a flaky coroutine-dump-verifying test #4589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,28 +2,27 @@ package kotlinx.coroutines.debug | |
|
|
||
| import kotlinx.coroutines.testing.* | ||
| import kotlinx.coroutines.* | ||
| import org.junit.* | ||
| import org.junit.Test | ||
| import kotlin.coroutines.* | ||
| import kotlin.test.* | ||
|
|
||
| class CoroutinesDumpTest : DebugTestBase() { | ||
| private val monitor = Any() | ||
| private var coroutineThread: Thread? = null // guarded by monitor | ||
|
|
||
| @Before | ||
| @BeforeTest | ||
| override fun setUp() { | ||
| super.setUp() | ||
| DebugProbes.enableCreationStackTraces = true | ||
| } | ||
|
|
||
| @Test | ||
| fun testSuspendedCoroutine() = runBlocking { | ||
| val deferred = async(Dispatchers.Default) { | ||
| sleepingOuterMethod() | ||
| val latch = CompletableDeferred<Unit>() | ||
| val singleThreadedDispatcher = newSingleThreadContext("TestCoroutineThread") | ||
| val deferred = async(singleThreadedDispatcher) { | ||
| sleepingOuterMethod(singleThreadedDispatcher, latch) | ||
| } | ||
|
|
||
| awaitCoroutine() | ||
| latch.await() | ||
| val found = DebugProbes.dumpCoroutinesInfo().single { it.job === deferred } | ||
| verifyDump( | ||
| "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: SUSPENDED\n" + | ||
|
|
@@ -35,6 +34,7 @@ class CoroutinesDumpTest : DebugTestBase() { | |
| "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt)\n", | ||
| ignoredCoroutine = "BlockingCoroutine" | ||
| ) { | ||
| singleThreadedDispatcher.close() | ||
| deferred.cancel() | ||
| coroutineThread!!.interrupt() | ||
| } | ||
|
|
@@ -181,29 +181,39 @@ class CoroutinesDumpTest : DebugTestBase() { | |
| private suspend fun nestedActiveMethod(shouldSuspend: Boolean) { | ||
| if (shouldSuspend) yield() | ||
| notifyCoroutineStarted() | ||
| while (coroutineContext[Job]!!.isActive) { | ||
| while (currentCoroutineContext()[Job]!!.isActive) { | ||
| try { | ||
| Thread.sleep(60_000) | ||
| } catch (_ : InterruptedException) { | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private suspend fun sleepingOuterMethod() { | ||
| sleepingNestedMethod() | ||
| yield() // TCE | ||
| private suspend fun sleepingOuterMethod(currentDispatcher: CoroutineDispatcher, latch: CompletableDeferred<Unit>) { | ||
| sleepingNestedMethod(currentDispatcher, latch) | ||
| yield() // TCE: make sure `sleepingOuterMethod` is contained in the continuation of `sleepingNestedMethod` | ||
| } | ||
|
|
||
| private suspend fun sleepingNestedMethod() { | ||
| yield() // Suspension point | ||
murfel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| notifyCoroutineStarted() | ||
| private suspend fun sleepingNestedMethod(currentDispatcher: CoroutineDispatcher, latch: CompletableDeferred<Unit>) { | ||
| /* Schedule a computation on the current single-threaded dispatcher. | ||
| Since that thread is currently running this code, | ||
| the start notification will happen *after* the currently running coroutine suspends. */ | ||
| currentDispatcher.dispatch(currentDispatcher) { | ||
| coroutineThread = Thread.currentThread() | ||
| latch.complete(Unit) | ||
| } | ||
| delay(Long.MAX_VALUE) | ||
| yield() // TCE: make sure `sleepingNestedMethod` is contained in the continuation of `delay` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this one avoids being inlined into
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm struggling to make the step from your comment to my understanding. Firstly, because I don't understand how's
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh wait, let me read your huge comment above, which I didn't see.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kind of see how it works now, but your comments skips a few steps of reasoning, so it's a bit hard to decipher without extra thinking or additional context. Could add info to your comment:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And I don't think that "TCE" comment is useful, really, either here or above. It is in fact tail-call elimination elimination, TCEE.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a balance to strike so that the useful information contained in a comment doesn't get obscured by restating the knowledge that's implicitly required. Using In any case, this discussion doesn't feel relevant to the PR we're discussing. If you would like us to improve the documentation of the places where we disable the tail call optimization of |
||
| } | ||
|
|
||
| private fun awaitCoroutine() = synchronized(monitor) { | ||
| while (coroutineThread == null) (monitor as Object).wait() | ||
| while (coroutineThread!!.state != Thread.State.TIMED_WAITING) { | ||
| // Wait until thread sleeps to have a consistent stacktrace | ||
| /* Wait until the thread we're awaiting goes to sleep. | ||
| Note: this does not establish a happens-before relationship with the thread entering `Thread.sleep`. | ||
| There still doesn't seem to be any guarantee that the stacktrace will be accurately updated. | ||
| <https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#getStackTrace--> says that how accurate | ||
| a stacktrace is is implementation-dependent. */ | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this one avoids being inlined into the test method, so that
sleepingOuterMethoddefinitely appears in the stackstrace