@@ -54,7 +54,6 @@ class ThreadContextElementTest : TestBase() {
54
54
assertNull(myThreadLocal.get())
55
55
}
56
56
57
-
58
57
@Test
59
58
fun testWithContext () = runTest {
60
59
expect(1 )
@@ -86,6 +85,78 @@ class ThreadContextElementTest : TestBase() {
86
85
87
86
finish(7 )
88
87
}
88
+
89
+ @Test
90
+ fun testNonCopyableElementReferenceInheritedOnLaunch () = runTest {
91
+ var parentElement: MyElement ? = null
92
+ var inheritedElement: MyElement ? = null
93
+
94
+ newSingleThreadContext(" withContext" ).use {
95
+ withContext(it + MyElement (MyData ())) {
96
+ parentElement = coroutineContext[MyElement .Key ]
97
+ launch {
98
+ inheritedElement = coroutineContext[MyElement .Key ]
99
+ }
100
+ }
101
+ }
102
+
103
+ assertSame(inheritedElement, parentElement,
104
+ " Inner and outer coroutines did not have the same object reference to a" +
105
+ " ThreadContextElement that did not override `copyForChildCoroutine()`" )
106
+ }
107
+
108
+ @Test
109
+ fun testCopyableElementCopiedOnLaunch () = runTest {
110
+ var parentElement: CopyForChildCoroutineElement ? = null
111
+ var inheritedElement: CopyForChildCoroutineElement ? = null
112
+
113
+ newSingleThreadContext(" withContext" ).use {
114
+ withContext(it + CopyForChildCoroutineElement (MyData ())) {
115
+ parentElement = coroutineContext[CopyForChildCoroutineElement .Key ]
116
+ launch {
117
+ inheritedElement = coroutineContext[CopyForChildCoroutineElement .Key ]
118
+ }
119
+ }
120
+ }
121
+
122
+ assertNotSame(inheritedElement, parentElement,
123
+ " Inner coroutine did not copy its copyable ThreadContextElement." )
124
+ }
125
+
126
+ @Test
127
+ fun testCopyableThreadContextElementImplementsWriteVisibility () = runTest {
128
+ newFixedThreadPoolContext(nThreads = 4 , name = " withContext" ).use {
129
+ val startData = MyData ()
130
+ withContext(it + CopyForChildCoroutineElement (startData)) {
131
+ val forBlockData = MyData ()
132
+ myThreadLocal.setForBlock(forBlockData) {
133
+ assertSame(myThreadLocal.get(), forBlockData)
134
+ launch {
135
+ assertSame(myThreadLocal.get(), forBlockData)
136
+ }
137
+ launch {
138
+ assertSame(myThreadLocal.get(), forBlockData)
139
+ // Modify value in child coroutine. Writes to the ThreadLocal and
140
+ // the (copied) ThreadLocalElement's memory are not visible to peer or
141
+ // ancestor coroutines, so this write is both threadsafe and coroutinesafe.
142
+ val innerCoroutineData = MyData ()
143
+ myThreadLocal.setForBlock(innerCoroutineData) {
144
+ assertSame(myThreadLocal.get(), innerCoroutineData)
145
+ }
146
+ assertSame(myThreadLocal.get(), forBlockData) // Asserts value was restored.
147
+ }
148
+ launch {
149
+ val innerCoroutineData = MyData ()
150
+ myThreadLocal.setForBlock(innerCoroutineData) {
151
+ assertSame(myThreadLocal.get(), innerCoroutineData)
152
+ }
153
+ assertSame(myThreadLocal.get(), forBlockData)
154
+ }
155
+ }
156
+ assertSame(myThreadLocal.get(), startData) // Asserts value was restored.
157
+ }
158
+ }
159
+ }
89
160
}
90
161
91
162
class MyData
@@ -114,3 +185,60 @@ class MyElement(val data: MyData) : ThreadContextElement<MyData?> {
114
185
myThreadLocal.set(oldState)
115
186
}
116
187
}
188
+
189
+ /* *
190
+ * A [ThreadContextElement] that implements copy semantics in [copyForChildCoroutine].
191
+ */
192
+ class CopyForChildCoroutineElement (val data : MyData ? ) : CopyableThreadContextElement<MyData?> {
193
+ companion object Key : CoroutineContext.Key<CopyForChildCoroutineElement>
194
+
195
+ override val key: CoroutineContext .Key <CopyForChildCoroutineElement >
196
+ get() = Key
197
+
198
+ override fun updateThreadContext (context : CoroutineContext ): MyData ? {
199
+ val oldState = myThreadLocal.get()
200
+ myThreadLocal.set(data)
201
+ return oldState
202
+ }
203
+
204
+ override fun restoreThreadContext (context : CoroutineContext , oldState : MyData ? ) {
205
+ myThreadLocal.set(oldState)
206
+ }
207
+
208
+ /* *
209
+ * At coroutine launch time, the _current value of the ThreadLocal_ is inherited by the new
210
+ * child coroutine, and that value is copied to a new, unique, ThreadContextElement memory
211
+ * reference for the child coroutine to use uniquely.
212
+ *
213
+ * n.b. the value copied to the child must be the __current value of the ThreadLocal__ and not
214
+ * the value initially passed to the ThreadContextElement in order to reflect writes made to the
215
+ * ThreadLocal between coroutine resumption and the child coroutine launch point. Those writes
216
+ * will be reflected in the parent coroutine's [CopyForChildCoroutineElement] when it yields the
217
+ * thread and calls [restoreThreadContext].
218
+ */
219
+ override fun copyForChildCoroutine (): CopyableThreadContextElement <MyData ?> {
220
+ return CopyForChildCoroutineElement (myThreadLocal.get())
221
+ }
222
+ }
223
+
224
+ /* *
225
+ * Calls [block], setting the value of [this] [ThreadLocal] for the duration of [block].
226
+ *
227
+ * When a [CopyForChildCoroutineElement] for `this` [ThreadLocal] is used within a
228
+ * [CoroutineContext], a ThreadLocal set this way will have the "correct" value expected lexically
229
+ * at every statement reached, whether that statement is reached immediately, across suspend and
230
+ * redispatch within one coroutine, or within a child coroutine. Writes made to the `ThreadLocal`
231
+ * by child coroutines will not be visible to the parent coroutine. Writes made to the `ThreadLocal`
232
+ * by the parent coroutine _after_ launching a child coroutine will not be visible to that child
233
+ * coroutine.
234
+ */
235
+ private inline fun <ThreadLocalT , OutputT > ThreadLocal<ThreadLocalT>.setForBlock (
236
+ value : ThreadLocalT ,
237
+ crossinline block : () -> OutputT
238
+ ) {
239
+ val priorValue = get()
240
+ set(value)
241
+ block()
242
+ set(priorValue)
243
+ }
244
+
0 commit comments