@@ -3,26 +3,13 @@ package kotlinx.coroutines
3
3
import kotlinx.coroutines.internal.*
4
4
import kotlin.coroutines.*
5
5
6
- /* *
7
- * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
8
- * [ContinuationInterceptor] is specified and adds optional support for debugging facilities (when turned on)
9
- * and copyable-thread-local facilities on JVM.
10
- */
11
- public expect fun CoroutineScope.newCoroutineContext (context : CoroutineContext ): CoroutineContext
12
-
13
- /* *
14
- * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext].
15
- * @suppress
16
- */
17
- @InternalCoroutinesApi
18
- public expect fun CoroutineContext.newCoroutineContext (addedContext : CoroutineContext ): CoroutineContext
19
-
20
6
@PublishedApi // to have unmangled name when using from other modules via suppress
21
7
@Suppress(" PropertyName" )
22
8
internal expect val DefaultDelay : Delay
23
9
24
10
internal expect fun Continuation <* >.toDebugString (): String
25
11
internal expect val CoroutineContext .coroutineName: String?
12
+ internal expect fun wrapContextWithDebug (context : CoroutineContext ): CoroutineContext
26
13
27
14
/* *
28
15
* Executes a block using a given coroutine context.
@@ -98,3 +85,81 @@ internal object UndispatchedMarker: CoroutineContext.Element, CoroutineContext.K
98
85
override val key: CoroutineContext .Key <* >
99
86
get() = this
100
87
}
88
+
89
+ /* *
90
+ * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
91
+ * [ContinuationInterceptor] is specified and
92
+ */
93
+ @ExperimentalCoroutinesApi
94
+ public fun CoroutineScope.newCoroutineContext (context : CoroutineContext ): CoroutineContext {
95
+ val combined = foldCopies(coroutineContext, context, true )
96
+ val debug = wrapContextWithDebug(combined)
97
+ return if (combined != = Dispatchers .Default && combined[ContinuationInterceptor ] == null )
98
+ debug + Dispatchers .Default else debug
99
+ }
100
+
101
+ /* *
102
+ * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext].
103
+ * @suppress
104
+ */
105
+ @InternalCoroutinesApi
106
+ public fun CoroutineContext.newCoroutineContext (addedContext : CoroutineContext ): CoroutineContext {
107
+ /*
108
+ * Fast-path: we only have to copy/merge if 'addedContext' (which typically has one or two elements)
109
+ * contains copyable elements.
110
+ */
111
+ if (! addedContext.hasCopyableElements()) return this + addedContext
112
+ return foldCopies(this , addedContext, false )
113
+ }
114
+
115
+ private fun CoroutineContext.hasCopyableElements (): Boolean =
116
+ fold(false ) { result, it -> result || it is CopyableThreadContextElement <* > }
117
+
118
+ /* *
119
+ * Folds two contexts properly applying [CopyableThreadContextElement] rules when necessary.
120
+ * The rules are the following:
121
+ * - If neither context has CTCE, the sum of two contexts is returned
122
+ * - Every CTCE from the left-hand side context that does not have a matching (by key) element from right-hand side context
123
+ * is [copied][CopyableThreadContextElement.copyForChild] if [isNewCoroutine] is `true`.
124
+ * - Every CTCE from the left-hand side context that has a matching element in the right-hand side context is [merged][CopyableThreadContextElement.mergeForChild]
125
+ * - Every CTCE from the right-hand side context that hasn't been merged is copied
126
+ * - Everything else is added to the resulting context as is.
127
+ */
128
+ private fun foldCopies (originalContext : CoroutineContext , appendContext : CoroutineContext , isNewCoroutine : Boolean ): CoroutineContext {
129
+ // Do we have something to copy left-hand side?
130
+ val hasElementsLeft = originalContext.hasCopyableElements()
131
+ val hasElementsRight = appendContext.hasCopyableElements()
132
+
133
+ // Nothing to fold, so just return the sum of contexts
134
+ if (! hasElementsLeft && ! hasElementsRight) {
135
+ return originalContext + appendContext
136
+ }
137
+
138
+ var leftoverContext = appendContext
139
+ val folded = originalContext.fold<CoroutineContext >(EmptyCoroutineContext ) { result, element ->
140
+ if (element !is CopyableThreadContextElement <* >) return @fold result + element
141
+ // Will this element be overwritten?
142
+ val newElement = leftoverContext[element.key]
143
+ // No, just copy it
144
+ if (newElement == null ) {
145
+ // For 'withContext'-like builders we do not copy as the element is not shared
146
+ return @fold result + if (isNewCoroutine) element.copyForChild() else element
147
+ }
148
+ // Yes, then first remove the element from append context
149
+ leftoverContext = leftoverContext.minusKey(element.key)
150
+ // Return the sum
151
+ @Suppress(" UNCHECKED_CAST" )
152
+ return @fold result + (element as CopyableThreadContextElement <Any ?>).mergeForChild(newElement)
153
+ }
154
+
155
+ if (hasElementsRight) {
156
+ leftoverContext = leftoverContext.fold<CoroutineContext >(EmptyCoroutineContext ) { result, element ->
157
+ // We're appending new context element -- we have to copy it, otherwise it may be shared with others
158
+ if (element is CopyableThreadContextElement <* >) {
159
+ return @fold result + element.copyForChild()
160
+ }
161
+ return @fold result + element
162
+ }
163
+ }
164
+ return folded + leftoverContext
165
+ }
0 commit comments