Skip to content

Commit 1460dfc

Browse files
committed
Introduce a rendezvous on CancellableContinuationImpl.parentHandle to avoid race with original thread setting parent handle, while another thread nulls it out during cancellation via detachChild
Fixes on of the races found in #3378
1 parent 5c582ee commit 1460dfc

File tree

1 file changed

+25
-4
lines changed

1 file changed

+25
-4
lines changed

kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt

+25-4
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,28 @@ internal open class CancellableContinuationImpl<in T>(
7070
*/
7171
private val _state = atomic<Any?>(Active)
7272

73-
private var parentHandle: DisposableHandle? = null
73+
/*
74+
* This field has a concurrent rendezvous in the following scenario:
75+
*
76+
* - installParentHandle publishes this instance on T1
77+
*
78+
* T1 writes:
79+
* * handle = installed; right after the installation
80+
* * Shortly after: if (isComplete) handle = NonDisposableHandle
81+
*
82+
* Any other T writes if the parent job is cancelled in detachChild:
83+
* * handle = NonDisposableHandle
84+
*
85+
* We want to preserve a strict invariant on parentHandle transition, allowing only three of them:
86+
* null -> anyHandle
87+
* anyHandle -> NonDisposableHandle
88+
* null -> NonDisposableHandle
89+
*
90+
* With a guarantee that after disposal the only state handle may end up in is NonDisposableHandle
91+
*/
92+
private val _parentHandle = atomic<DisposableHandle?>(null)
93+
private val parentHandle: DisposableHandle?
94+
get() = _parentHandle.value
7495

7596
internal val state: Any? get() = _state.value
7697

@@ -101,7 +122,7 @@ internal open class CancellableContinuationImpl<in T>(
101122
if (isCompleted) {
102123
// Can be invoked concurrently in 'parentCancelled', no problems here
103124
handle.dispose()
104-
parentHandle = NonDisposableHandle
125+
_parentHandle.value = NonDisposableHandle
105126
}
106127
}
107128

@@ -307,7 +328,7 @@ internal open class CancellableContinuationImpl<in T>(
307328
onCancelling = true,
308329
handler = ChildContinuation(this).asHandler
309330
)
310-
parentHandle = handle
331+
_parentHandle.compareAndSet(null, handle)
311332
return handle
312333
}
313334

@@ -492,7 +513,7 @@ internal open class CancellableContinuationImpl<in T>(
492513
internal fun detachChild() {
493514
val handle = parentHandle ?: return
494515
handle.dispose()
495-
parentHandle = NonDisposableHandle
516+
_parentHandle.value = NonDisposableHandle
496517
}
497518

498519
// Note: Always returns RESUME_TOKEN | null

0 commit comments

Comments
 (0)