Skip to content
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

Integrate event loops of runBlocking and UNCONFINED #860

Closed
elizarov opened this issue Nov 27, 2018 · 1 comment
Closed

Integrate event loops of runBlocking and UNCONFINED #860

elizarov opened this issue Nov 27, 2018 · 1 comment

Comments

@elizarov
Copy link
Contributor

The problem is that the code that uses runBlocking { ... } to provide integration for blocking Java APIs (like blocking IO) can deadlock when being run inside another runBlocking { ... } (see ktorio/ktor#672 for a real-life problem in Ktor that is causes). To avoid that, a nested runBlocking shall use the same event queue as the outer runBlocking. The same is true for Dispatchers.UNCONFINED. There should be at most one active event queue per each thread for the purposes of runBlocking and UNCONFINED, which would allow to mix them in a deadlock-free way.

One test that shows the problem is:

fun main() = runBlocking { // outer event loop
    // Produce string "OK"
    val ch = produce { send("OK") }
    // try receive this string in a blocking way:
    println(runBlocking { ch.receive() }) // it hangs here !!!
}
@elizarov
Copy link
Contributor Author

Note, that UNCONFINED dispatcher shall still keep its existing behavior when used from inside runBlocking { ... } in that a top-level execution of an unconfined task shall wait until all unconfined task in the queue are executed before exiting. Use case:

Consider a regular function with an injected coroutine context

fun launchFoo(ctx: CoroutineContext) = launch(ctx) { ... }

We are writing a test for it like this:

fun testFoo() {
    launchFoo(Dispatchers.UNCONFINED) 
    // ^^^ foo's job completes before executing the next line
    assert { fooDidEverything }
}

Wrapping this test function into a runBlocking { .... } should not invalidate the test:

fun testFoo() = runBlocking<Unit> {
    launchFoo(Dispatchers.UNCONFINED) 
    // ^^^ foo's job completes before executing the next line
    assert { fooDidEverything }
}

elizarov added a commit that referenced this issue Dec 12, 2018
elizarov added a commit that referenced this issue Dec 13, 2018
…chers

- Event loop that is created by runBlocking or by Unconfined dispatcher
  is reused across the same thread to prevent blocking of the loop
- Semantics of runBlocking and Unconfined are fully retained
- DefaultExecutor also registers itself as running event loop and thus
  cannot be blocked by runBlocking
- Consolidates thread-local handling for native
- Also fixes thread-local memory leak on JVM (does not use custom class)

Fixes #860
elizarov added a commit that referenced this issue Dec 13, 2018
…chers

- Event loop that is created by runBlocking or by Unconfined dispatcher
  is reused across the same thread to prevent blocking of the loop
- Semantics of runBlocking and Unconfined are fully retained
- DefaultExecutor also registers itself as running event loop and thus
  cannot be blocked by runBlocking
- Consolidates thread-local handling for native
- Also fixes thread-local memory leak on JVM (does not use custom class)

Fixes #860
elizarov added a commit that referenced this issue Dec 13, 2018
…chers

- Event loop that is created by runBlocking or by Unconfined dispatcher
  is reused across the same thread to prevent blocking of the loop
- Semantics of runBlocking and Unconfined are fully retained
- DefaultExecutor also registers itself as running event loop and thus
  cannot be blocked by runBlocking
- Consolidates thread-local handling for native
- Also fixes thread-local memory leak on JVM (does not use custom class)

Fixes #860
elizarov added a commit that referenced this issue Dec 13, 2018
…chers

- Event loop that is created by runBlocking or by Unconfined dispatcher
  is reused across the same thread to prevent blocking of the loop
- Semantics of runBlocking and Unconfined are fully retained
- DefaultExecutor also registers itself as running event loop and thus
  cannot be blocked by runBlocking
- Consolidates thread-local handling for native
- Also fixes thread-local memory leak on JVM (does not use custom class)

Fixes #860
elizarov added a commit that referenced this issue Dec 13, 2018
…chers

- Event loop that is created by runBlocking or by Unconfined dispatcher
  is reused across the same thread to prevent blocking of the loop
- Semantics of runBlocking and Unconfined are fully retained
- DefaultExecutor also registers itself as running event loop and thus
  cannot be blocked by runBlocking
- Consolidates thread-local handling for native
- Also fixes thread-local memory leak on JVM (does not use custom class)

Fixes #860
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

1 participant