@@ -52,8 +52,45 @@ public actual fun <T> runBlocking(context: CoroutineContext, block: suspend Coro
52
52
newContext = GlobalScope .newCoroutineContext(context)
53
53
}
54
54
val coroutine = BlockingCoroutine <T >(newContext, eventLoop)
55
- coroutine.start(CoroutineStart .DEFAULT , coroutine, block)
56
- return coroutine.joinBlocking()
55
+ var completed = false
56
+ ThreadLocalKeepAlive .addCheck { ! completed }
57
+ try {
58
+ coroutine.start(CoroutineStart .DEFAULT , coroutine, block)
59
+ return coroutine.joinBlocking()
60
+ } finally {
61
+ completed = true
62
+ }
63
+ }
64
+
65
+ @ThreadLocal
66
+ private object ThreadLocalKeepAlive {
67
+ /* * If any of these checks passes, this means this [Worker] is still used. */
68
+ private var checks = mutableListOf< () -> Boolean > ()
69
+
70
+ /* * Whether the worker currently tries to keep itself alive. */
71
+ private var keepAliveLoopActive = false
72
+
73
+ /* * Adds another stopgap that must be passed before the [Worker] can be terminated. */
74
+ fun addCheck (terminationForbidden : () -> Boolean ) {
75
+ checks.add(terminationForbidden)
76
+ if (! keepAliveLoopActive) keepAlive()
77
+ }
78
+
79
+ /* *
80
+ * Send a ping to the worker to prevent it from terminating while this coroutine is running,
81
+ * ensuring that continuations don't get dropped and forgotten.
82
+ */
83
+ private fun keepAlive () {
84
+ // only keep the checks that still forbid the termination
85
+ checks = checks.filter { it() }.toMutableList()
86
+ // if there are no checks left, we no longer keep the worker alive, it can be terminated
87
+ keepAliveLoopActive = checks.isNotEmpty()
88
+ if (keepAliveLoopActive) {
89
+ Worker .current.executeAfter(afterMicroseconds = 100_000 ) {
90
+ keepAlive()
91
+ }
92
+ }
93
+ }
57
94
}
58
95
59
96
private class BlockingCoroutine <T >(
0 commit comments