@@ -7,87 +7,6 @@ package kotlinx.coroutines
7
7
import kotlinx.coroutines.internal.*
8
8
import kotlin.coroutines.*
9
9
10
- internal expect fun propagateExceptionToPlatform (context : CoroutineContext , exception : Throwable )
11
-
12
- /* *
13
- * If [addOnExceptionCallback] is called, the provided callback will be evaluated each time
14
- * [handleCoroutineException] is executed and can't find a [CoroutineExceptionHandler] to
15
- * process the exception.
16
- *
17
- * When a callback is registered once, even if it's later removed, the system starts to assume that
18
- * other callbacks will eventually be registered, and so collects the exceptions.
19
- * Once a new callback is registered, it tries.
20
- *
21
- * The callbacks in this object are the last resort before relying on platform-dependent
22
- * ways to report uncaught exceptions from coroutines.
23
- */
24
- @PublishedApi
25
- internal object ExceptionCollector {
26
- private val lock = SynchronizedObject ()
27
- private var enabled = false
28
- private var unprocessedExceptions = mutableListOf<Throwable >()
29
- private val callbacks = mutableMapOf<Any , (Throwable ) - > Unit > ()
30
-
31
- /* *
32
- * Registers [callback] to be executed when an uncaught exception happens.
33
- * [owner] is a key by which to distinguish different callbacks.
34
- */
35
- fun addOnExceptionCallback (owner : Any , callback : (Throwable ) -> Unit ) = synchronized(lock) {
36
- enabled = true // never becomes `false` again
37
- val previousValue = callbacks.put(owner, callback)
38
- assert { previousValue == = null }
39
- // try to process the exceptions using the newly-registered callback
40
- unprocessedExceptions.forEach { reportException(it) }
41
- unprocessedExceptions = mutableListOf ()
42
- }
43
-
44
- /* *
45
- * Unregisters the callback associated with [owner].
46
- */
47
- fun removeOnExceptionCallback (owner : Any ) = synchronized(lock) {
48
- val existingValue = callbacks.remove(owner)
49
- assert { existingValue != = null }
50
- }
51
-
52
- /* *
53
- * Tries to handle the exception by propagating it to an interested consumer.
54
- * Returns `true` if the exception does not need further processing.
55
- *
56
- * Doesn't throw.
57
- */
58
- fun handleException (exception : Throwable ): Boolean = synchronized(lock) {
59
- if (! enabled) return false
60
- if (reportException(exception)) return true
61
- /* * we don't return the result of the `add` function because we don't have a guarantee
62
- * that a callback will eventually appear and collect the unprocessed exceptions, so
63
- * we can't consider [exception] to be properly handled. */
64
- unprocessedExceptions.add(exception)
65
- return false
66
- }
67
-
68
- /* *
69
- * Try to report [exception] to the existing callbacks.
70
- */
71
- private fun reportException (exception : Throwable ): Boolean {
72
- var executedACallback = false
73
- for (callback in callbacks.values) {
74
- callback(exception)
75
- executedACallback = true
76
- /* * We don't leave the function here because we want to fan-out the exceptions to every interested consumer,
77
- * it's not enough to have the exception processed by one of them.
78
- * The reason is, it's less big of a deal to observe multiple concurrent reports of bad behavior than not
79
- * to observe the report in the exact callback that is connected to that bad behavior. */
80
- }
81
- return executedACallback
82
- }
83
- }
84
-
85
- internal fun handleUncaughtCoroutineException (context : CoroutineContext , exception : Throwable ) {
86
- // TODO: if ANDROID_DETECTED
87
- if (! ExceptionCollector .handleException(exception))
88
- propagateExceptionToPlatform(context, exception)
89
- }
90
-
91
10
/* *
92
11
* Helper function for coroutine builder implementations to handle uncaught and unexpected exceptions in coroutines,
93
12
* that could not be otherwise handled in a normal way through structured concurrency, saving them to a future, and
0 commit comments