@@ -29,15 +29,22 @@ internal actual class SafeCollector<T> actual constructor(
2929
3030 @JvmField // Note, it is non-capturing lambda, so no extra allocation during init of SafeCollector
3131 internal actual val collectContextSize = collectContext.fold(0 ) { count, _ -> count + 1 }
32+
33+ // Either context of the last emission or wrapper 'DownstreamExceptionContext'
3234 private var lastEmissionContext: CoroutineContext ? = null
35+ // Completion if we are currently suspended or within completion body or null otherwise
3336 private var completion: Continuation <Unit >? = null
3437
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+ */
3643 override val context: CoroutineContext
37- get() = completion?.context ? : EmptyCoroutineContext
44+ get() = lastEmissionContext ? : EmptyCoroutineContext
3845
3946 override fun invokeSuspend (result : Result <Any ?>): Any {
40- result.onFailure { lastEmissionContext = DownstreamExceptionElement (it) }
47+ result.onFailure { lastEmissionContext = DownstreamExceptionContext (it, context ) }
4148 completion?.resumeWith(result as Result <Unit >)
4249 return COROUTINE_SUSPENDED
4350 }
@@ -59,7 +66,9 @@ internal actual class SafeCollector<T> actual constructor(
5966 emit(uCont, value)
6067 } catch (e: Throwable ) {
6168 // 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)
6372 throw e
6473 }
6574 }
@@ -72,24 +81,32 @@ internal actual class SafeCollector<T> actual constructor(
7281 val previousContext = lastEmissionContext
7382 if (previousContext != = currentContext) {
7483 checkContext(currentContext, previousContext, value)
84+ lastEmissionContext = currentContext
7585 }
7686 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
7896 }
7997
8098 private fun checkContext (
8199 currentContext : CoroutineContext ,
82100 previousContext : CoroutineContext ? ,
83101 value : T
84102 ) {
85- if (previousContext is DownstreamExceptionElement ) {
103+ if (previousContext is DownstreamExceptionContext ) {
86104 exceptionTransparencyViolated(previousContext, value)
87105 }
88106 checkContext(currentContext)
89- lastEmissionContext = currentContext
90107 }
91108
92- private fun exceptionTransparencyViolated (exception : DownstreamExceptionElement , value : Any? ) {
109+ private fun exceptionTransparencyViolated (exception : DownstreamExceptionContext , value : Any? ) {
93110 /*
94111 * Exception transparency ensures that if a `collect` block or any intermediate operator
95112 * throws an exception, then no more values will be received by it.
@@ -122,14 +139,12 @@ internal actual class SafeCollector<T> actual constructor(
122139 For a more detailed explanation, please refer to Flow documentation.
123140 """ .trimIndent())
124141 }
125-
126142}
127143
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
133148
134149private object NoOpContinuation : Continuation<Any?> {
135150 override val context: CoroutineContext = EmptyCoroutineContext
0 commit comments