Skip to content

Commit

Permalink
Clarify that using runBlocking in suspend functions is allowed
Browse files Browse the repository at this point in the history
A user raised a concern that the internal coroutines machinery may
break if `runBlocking` is used somewhere deeply in the call stack.
Calling `runBlocking` from a `suspend` functions can lead to
deadlocks naturally due to blocking the thread, or to surprising
event ordering, but nothing is expected to break.

This commit clarifies the exact danger of calling `runBlocking`
from `suspend` functions.
  • Loading branch information
dkhalanskyjb committed Feb 15, 2024
1 parent d0dabb9 commit fdc0818
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 7 deletions.
20 changes: 17 additions & 3 deletions kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,21 @@ import kotlin.coroutines.*

/**
* Runs a new coroutine and **blocks** the current thread until its completion.
* This function should not be used from a coroutine. It is designed to bridge regular blocking code
* to libraries that are written in suspending style, to be used in `main` functions and in tests.
*
* It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in
* `main` functions and in tests.
*
* Calling [runBlocking] from a suspend function is redundant.
* For example, the following code is incorrect:
* ```
* suspend fun loadConfiguration() {
* // DO NOT DO THIS:
* val data = runBlocking { // <- redundant and blocks the thread, do not do that
* fetchConfigurationData() // suspending function
* }
* ```
*
* Here, instead of releasing the thread on which `loadConfiguration` runs if `fetchConfigurationData` suspends, it will
* block, potentially leading to thread starvation issues.
*/
public expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
public expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
18 changes: 16 additions & 2 deletions kotlinx-coroutines-core/jvm/src/Builders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,22 @@ import kotlin.coroutines.*

/**
* Runs a new coroutine and **blocks** the current thread _interruptibly_ until its completion.
* This function should not be used from a coroutine. It is designed to bridge regular blocking code
* to libraries that are written in suspending style, to be used in `main` functions and in tests.
*
* It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in
* `main` functions and in tests.
*
* Calling [runBlocking] from a suspend function is redundant.
* For example, the following code is incorrect:
* ```
* suspend fun loadConfiguration() {
* // DO NOT DO THIS:
* val data = runBlocking { // <- redundant and blocks the thread, do not do that
* fetchConfigurationData() // suspending function
* }
* ```
*
* Here, instead of releasing the thread on which `loadConfiguration` runs if `fetchConfigurationData` suspends, it will
* block, potentially leading to thread starvation issues.
*
* The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations
* in this blocked thread until the completion of this coroutine.
Expand Down
18 changes: 16 additions & 2 deletions kotlinx-coroutines-core/native/src/Builders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,22 @@ import kotlin.native.concurrent.*

/**
* Runs new coroutine and **blocks** current thread _interruptibly_ until its completion.
* This function should not be used from coroutine. It is designed to bridge regular blocking code
* to libraries that are written in suspending style, to be used in `main` functions and in tests.
*
* It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in
* `main` functions and in tests.
*
* Calling [runBlocking] from a suspend function is redundant.
* For example, the following code is incorrect:
* ```
* suspend fun loadConfiguration() {
* // DO NOT DO THIS:
* val data = runBlocking { // <- redundant and blocks the thread, do not do that
* fetchConfigurationData() // suspending function
* }
* ```
*
* Here, instead of releasing the thread on which `loadConfiguration` runs if `fetchConfigurationData` suspends, it will
* block, potentially leading to thread starvation issues.
*
* The default [CoroutineDispatcher] for this builder in an implementation of [EventLoop] that processes continuations
* in this blocked thread until the completion of this coroutine.
Expand Down

0 comments on commit fdc0818

Please sign in to comment.