@@ -12,10 +12,80 @@ import kotlin.jvm.JvmMultifileClass
12
12
import kotlin.jvm.JvmName
13
13
14
14
/* *
15
- * Runs a new coroutine and **blocks** the current thread until its completion.
15
+ * Runs the given [block] in-place in a new coroutine based on [context],
16
+ * **blocking the current thread** until its completion, and then returning its result.
16
17
*
17
18
* It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in
18
- * `main` functions and in tests.
19
+ * `main` functions, in tests, and in non-`suspend` callbacks when `suspend` functions need to be called.
20
+ *
21
+ * On the JVM, if this blocked thread is interrupted (see `java.lang.Thread.interrupt`),
22
+ * then the coroutine job is cancelled and this `runBlocking` invocation throws an `InterruptedException`.
23
+ * On Kotlin/Native, there is no way to interrupt a thread.
24
+ *
25
+ * ## Structured concurrency
26
+ *
27
+ * The lifecycle of the new coroutine's [Job] begins with starting the [block] and completes when both the [block] and
28
+ * all the coroutines launched in the scope complete.
29
+ *
30
+ * A new coroutine is created with the following properties:
31
+ * - A new [Job] for a lexically scoped coroutine is created.
32
+ * Its parent is the [Job] from the [context], if any was passed.
33
+ * - If a [ContinuationInterceptor] is passed in [context],
34
+ * it is used as a dispatcher of the new coroutine created by [runBlocking].
35
+ * Otherwise, the new coroutine is dispatched to an event loop opened on this thread.
36
+ * - The other pieces of the context are put into the new coroutine context as is.
37
+ * - [newCoroutineContext] is called to optionally install debugging facilities.
38
+ *
39
+ * The resulting context is available in the [CoroutineScope] passed as the [block]'s receiver.
40
+ *
41
+ * Because the new coroutine is lexically scoped, even if a [Job] was passed in the [context],
42
+ * it will not be cancelled if [runBlocking] or some child coroutine fails with an exception.
43
+ * Instead, the exception will be rethrown to the caller of this function.
44
+ *
45
+ * If any child coroutine in this scope fails with an exception,
46
+ * the scope fails, cancelling all the other children and its own [block].
47
+ * If children should fail independently, consider using [supervisorScope]:
48
+ * ```
49
+ * runBlocking(CoroutineExceptionHandler { _, e ->
50
+ * // handle the exception
51
+ * }) {
52
+ * supervisorScope {
53
+ * // Children fail independently here
54
+ * }
55
+ * }
56
+ * ```
57
+ *
58
+ * Rephrasing this in more practical terms, the specific list of structured concurrency interactions is as follows:
59
+ * - The caller's [currentCoroutineContext] *is not taken into account*, its cancellation does not affect [runBlocking].
60
+ * - If the new [CoroutineScope] fails with an exception
61
+ * (which happens if either its [block] or any child coroutine fails with an exception),
62
+ * the exception is rethrown to the caller,
63
+ * without affecting the [Job] passed in the [context] (if any).
64
+ * Note that this happens on any child coroutine's failure even if [block] finishes successfully.
65
+ * - Cancelling the [Job] passed in the [context] (if any) cancels the new coroutine and its children.
66
+ * - [runBlocking] will only finish when all the coroutines launched in it finish.
67
+ * If all of them complete without failing, the [runBlocking] returns the result of the [block] to the caller.
68
+ *
69
+ * ## Event loop
70
+ *
71
+ * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes
72
+ * continuations in this blocked thread until the completion of this coroutine.
73
+ *
74
+ * This event loop is set in a thread-local variable and is accessible to nested [runBlocking] calls and
75
+ * coroutine tasks forming an event loop
76
+ * (such as the tasks of [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate]).
77
+ *
78
+ * Nested [runBlocking] calls may execute other coroutines' tasks instead of running their own tasks.
79
+ *
80
+ * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of
81
+ * the specified dispatcher while the current thread is blocked (and possibly running tasks from other
82
+ * [runBlocking] calls on the same thread or [Dispatchers.Unconfined]).
83
+ *
84
+ * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`.
85
+ *
86
+ * ## Pitfalls
87
+ *
88
+ * ### Calling from a suspend function
19
89
*
20
90
* Calling [runBlocking] from a suspend function is redundant.
21
91
* For example, the following code is incorrect:
@@ -25,27 +95,72 @@ import kotlin.jvm.JvmName
25
95
* val data = runBlocking { // <- redundant and blocks the thread, do not do that
26
96
* fetchConfigurationData() // suspending function
27
97
* }
98
+ * // ...
28
99
* ```
29
100
*
30
101
* Here, instead of releasing the thread on which `loadConfiguration` runs if `fetchConfigurationData` suspends, it will
31
102
* block, potentially leading to thread starvation issues.
103
+ * Additionally, the [currentCoroutineContext] will be ignored, and the new computation will run in the context of
104
+ * the new `runBlocking` coroutine.
32
105
*
33
- * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations
34
- * in this blocked thread until the completion of this coroutine.
35
- * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`.
106
+ * Instead, write it like this:
36
107
*
37
- * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of
38
- * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`,
39
- * then this invocation uses the outer event loop.
108
+ * ```
109
+ * suspend fun loadConfiguration() {
110
+ * val data = fetchConfigurationData() // suspending function
111
+ * // ...
112
+ * ```
113
+ *
114
+ * ### Sharing tasks between [runBlocking] calls
115
+ *
116
+ * The event loop used by [runBlocking] is shared with the other [runBlocking] calls.
117
+ * This can lead to surprising and undesired behavior.
118
+ *
119
+ * ```
120
+ * runBlocking {
121
+ * val job = launch {
122
+ * delay(50.milliseconds)
123
+ * println("Hello from the outer child coroutine")
124
+ * }
125
+ * runBlocking {
126
+ * println("Entered the inner runBlocking")
127
+ * delay(100.milliseconds)
128
+ * println("Leaving the inner runBlocking")
129
+ * }
130
+ * }
131
+ * ```
132
+ *
133
+ * This outputs the following:
134
+ *
135
+ * ```
136
+ * Entered the inner runBlocking
137
+ * Hello from the outer child coroutine
138
+ * Leaving the inner runBlocking
139
+ * ```
140
+ *
141
+ * For example, the following code may fail with a stack overflow error:
40
142
*
41
- * If this blocked thread is interrupted (see `Thread.interrupt`), then the coroutine job is cancelled and
42
- * this `runBlocking` invocation throws `InterruptedException`.
143
+ * ```
144
+ * runBlocking {
145
+ * repeat(1000) {
146
+ * launch {
147
+ * try {
148
+ * runBlocking {
149
+ * // do nothing
150
+ * }
151
+ * } catch (e: Throwable) {
152
+ * println(e)
153
+ * }
154
+ * }
155
+ * }
156
+ * }
157
+ * ```
43
158
*
44
- * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available
45
- * for a newly created coroutine .
159
+ * The reason is that each new `runBlocking` attempts to run the task of the outer `runBlocking` coroutine inline,
160
+ * but those, in turn, start new `runBlocking` calls .
46
161
*
47
- * @param context the context of the coroutine. The default value is an event loop on the current thread.
48
- * @param block the coroutine code .
162
+ * The specific behavior of work stealing may change in the future, but is unlikely to be fully fixed,
163
+ * given how widespread [runBlocking] is .
49
164
*/
50
165
@OptIn(ExperimentalContracts ::class )
51
166
@JvmName(" runBlockingK" )
0 commit comments