Skip to content

CoroutineStart.UNDISPATCHED may cause withContext to not perform thread switch #286

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

Open
jcornaz opened this issue Mar 14, 2018 · 2 comments

Comments

@jcornaz
Copy link
Contributor

jcornaz commented Mar 14, 2018

Hello,

please consider the following example:

fun main(args: Array<String>) = runBlocking<Unit> {
  launch(coroutineContext + CommonPool, start = CoroutineStart.UNDISPATCHED) {
    println("I am in ${Thread.currentThread().name}")
    withContext(CommonPool) {
      println("I am in ${Thread.currentThread().name}")
    }
    println("I am in ${Thread.currentThread().name}")
  }
}

This code prints:

I am in main
I am in main
I am in main

I think it is a bit astonishing. I would have expected:

I am in main
I am in ForkJoinPool.commonPool-worker-1
I am in ForkJoinPool.commonPool-worker-1

The first print has to be in "main" because of the undispatched start. But I would expect withContext to perform the necessary thread switch.

I understand the reason is that even started undispatched, the context is actually already CommonPool and that's why withContext use a fast path, and do not perform thread switch.

However, I think this may cause programming mistakes. Would it be possible somehow, to improve withContext to avoid such mistakes?

@jcornaz jcornaz changed the title CoroutineStart.UNDISPATCHED may make withContext not performing thread switch CoroutineStart.UNDISPATCHED may cause withContext not performing thread switch Mar 14, 2018
@jcornaz jcornaz changed the title CoroutineStart.UNDISPATCHED may cause withContext not performing thread switch CoroutineStart.UNDISPATCHED may cause withContext to not perform thread switch Mar 14, 2018
@elizarov
Copy link
Contributor

elizarov commented Mar 14, 2018

It is going to be hard to fix, since we don't currently have a mechanism for withContext to figure out that it was running in an UNDISPATCHED coroutine (before its first suspension). Consider slightly modified code:

fun main(args: Array<String>) = runBlocking<Unit> {
  launch(coroutineContext + CommonPool, start = CoroutineStart.UNDISPATCHED) {
    withContext(CommonPool) { /* 1st */ }
    yield()
    withContext(CommonPool) { /* 2st */ }
  }
}

The first withContext is still running in main, but it does not know about it (it only knows that its parent coroutine has CommonPool as its context), while the second withContext is already in the CommonPool context. Somehow, we need coroutine to know whether it was already dispatched to the appropriate context or not. We don't have this kind of feature at the moment.

@dovchinnikov
Copy link
Contributor

So, withContext does not know about UNDISPATCHED. May be it's time to teach it :)
This knowledge will help to solve #3681 as well:

launch(UNDISPATCHED) {
  myStateFlow.collectLatest {
    // collectLatest can detect that no suspension is needed for the first value 
    // and execute the collector immediately in the same stack frame 
    // if the calling coroutine is UNDISPATCHED
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants