@@ -29,15 +29,22 @@ internal actual class SafeCollector<T> actual constructor(
29
29
30
30
@JvmField // Note, it is non-capturing lambda, so no extra allocation during init of SafeCollector
31
31
internal actual val collectContextSize = collectContext.fold(0 ) { count, _ -> count + 1 }
32
+
33
+ // Either context of the last emission or wrapper 'DownstreamExceptionContext'
32
34
private var lastEmissionContext: CoroutineContext ? = null
35
+ // Completion if we are currently suspended or within completion body or null otherwise
33
36
private var completion: Continuation <Unit >? = null
34
37
35
- // ContinuationImpl
38
+ /*
39
+ * This property is accessed in two places:
40
+ * * ContinuationImpl invokes this in its `releaseIntercepted` as `context[ContinuationInterceptor]!!`
41
+ * * When we are within a callee, it is used to create its continuation object with this collector as completion
42
+ */
36
43
override val context: CoroutineContext
37
- get() = completion?.context ? : EmptyCoroutineContext
44
+ get() = lastEmissionContext ? : EmptyCoroutineContext
38
45
39
46
override fun invokeSuspend (result : Result <Any ?>): Any {
40
- result.onFailure { lastEmissionContext = DownstreamExceptionElement (it) }
47
+ result.onFailure { lastEmissionContext = DownstreamExceptionContext (it, context ) }
41
48
completion?.resumeWith(result as Result <Unit >)
42
49
return COROUTINE_SUSPENDED
43
50
}
@@ -59,7 +66,9 @@ internal actual class SafeCollector<T> actual constructor(
59
66
emit(uCont, value)
60
67
} catch (e: Throwable ) {
61
68
// Save the fact that exception from emit (or even check context) has been thrown
62
- lastEmissionContext = DownstreamExceptionElement (e)
69
+ // Note, that this can the first emit and lastEmissionContext may not be saved yet,
70
+ // hence we use `uCont.context` here.
71
+ lastEmissionContext = DownstreamExceptionContext (e, uCont.context)
63
72
throw e
64
73
}
65
74
}
@@ -72,24 +81,32 @@ internal actual class SafeCollector<T> actual constructor(
72
81
val previousContext = lastEmissionContext
73
82
if (previousContext != = currentContext) {
74
83
checkContext(currentContext, previousContext, value)
84
+ lastEmissionContext = currentContext
75
85
}
76
86
completion = uCont
77
- return emitFun(collector as FlowCollector <Any ?>, value, this as Continuation <Unit >)
87
+ val result = emitFun(collector as FlowCollector <Any ?>, value, this as Continuation <Unit >)
88
+ /*
89
+ * If the callee hasn't suspended, that means that it won't (it's forbidden) call 'resumeWith` (-> `invokeSuspend`)
90
+ * and we don't have to retain a strong reference to it to avoid memory leaks.
91
+ */
92
+ if (result != COROUTINE_SUSPENDED ) {
93
+ completion = null
94
+ }
95
+ return result
78
96
}
79
97
80
98
private fun checkContext (
81
99
currentContext : CoroutineContext ,
82
100
previousContext : CoroutineContext ? ,
83
101
value : T
84
102
) {
85
- if (previousContext is DownstreamExceptionElement ) {
103
+ if (previousContext is DownstreamExceptionContext ) {
86
104
exceptionTransparencyViolated(previousContext, value)
87
105
}
88
106
checkContext(currentContext)
89
- lastEmissionContext = currentContext
90
107
}
91
108
92
- private fun exceptionTransparencyViolated (exception : DownstreamExceptionElement , value : Any? ) {
109
+ private fun exceptionTransparencyViolated (exception : DownstreamExceptionContext , value : Any? ) {
93
110
/*
94
111
* Exception transparency ensures that if a `collect` block or any intermediate operator
95
112
* throws an exception, then no more values will be received by it.
@@ -122,14 +139,12 @@ internal actual class SafeCollector<T> actual constructor(
122
139
For a more detailed explanation, please refer to Flow documentation.
123
140
""" .trimIndent())
124
141
}
125
-
126
142
}
127
143
128
- internal class DownstreamExceptionElement (@JvmField val e : Throwable ) : CoroutineContext.Element {
129
- companion object Key : CoroutineContext.Key<DownstreamExceptionElement>
130
-
131
- override val key: CoroutineContext .Key <* > = Key
132
- }
144
+ internal class DownstreamExceptionContext (
145
+ @JvmField val e : Throwable ,
146
+ originalContext : CoroutineContext
147
+ ) : CoroutineContext by originalContext
133
148
134
149
private object NoOpContinuation : Continuation<Any?> {
135
150
override val context: CoroutineContext = EmptyCoroutineContext
0 commit comments