@@ -106,7 +106,8 @@ internal open class CancellableContinuationImpl<in T>(
106
106
_state .loop { state ->
107
107
if (state !is NotCompleted ) return false // false if already complete or cancelling
108
108
// Active -- update to final state
109
- if (! _state .compareAndSet(state, CancelledContinuation (this , cause))) return @loop // retry on cas failure
109
+ val update = CancelledContinuation (this , cause, handled = state is CancelHandler )
110
+ if (! _state .compareAndSet(state, update)) return @loop // retry on cas failure
110
111
// Invoke cancel handler if it was present
111
112
if (state is CancelHandler ) invokeHandlerSafely { state.invoke(cause) }
112
113
// Complete state update
@@ -177,28 +178,37 @@ internal open class CancellableContinuationImpl<in T>(
177
178
val node = handleCache ? : makeHandler(handler).also { handleCache = it }
178
179
if (_state .compareAndSet(state, node)) return // quit on cas success
179
180
}
180
- is CancelHandler -> {
181
- error(" It's prohibited to register multiple handlers, tried to register $handler , already has $state " )
182
- }
181
+ is CancelHandler -> multipleHandlersError(handler, state)
183
182
is CancelledContinuation -> {
184
183
/*
185
184
* Continuation was already cancelled, invoke directly.
186
- * NOTE: multiple invokeOnCancellation calls with different handlers are allowed on cancelled continuation.
187
- * It's inconsistent with running continuation, but currently, we have no mechanism to check
188
- * whether any handler was registered during continuation lifecycle without additional overhead.
189
- * This may be changed in the future.
190
- *
185
+ * NOTE: multiple invokeOnCancellation calls with different handlers are not allowed,
186
+ * so we check to make sure that handler was installed just once.
187
+ */
188
+ if ( ! state.makeHandled()) multipleHandlersError(handler, state)
189
+ / *
191
190
* :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension,
192
191
* because we play type tricks on Kotlin/JS and handler is not necessarily a function there
193
192
*/
194
193
invokeHandlerSafely { handler.invokeIt((state as ? CompletedExceptionally )?.cause) }
195
194
return
196
195
}
197
- else -> return
196
+ else -> {
197
+ /*
198
+ * Continuation was already completed, do nothing.
199
+ * NOTE: multiple invokeOnCancellation calls with different handlers are not allowed,
200
+ * but we have no way to check that it was installed just once in this case.
201
+ */
202
+ return
203
+ }
198
204
}
199
205
}
200
206
}
201
207
208
+ private fun multipleHandlersError (handler : CompletionHandler , state : Any? ) {
209
+ error(" It's prohibited to register multiple handlers, tried to register $handler , already has $state " )
210
+ }
211
+
202
212
private fun makeHandler (handler : CompletionHandler ): CancelHandler =
203
213
if (handler is CancelHandler ) handler else InvokeOnCancel (handler)
204
214
@@ -219,20 +229,21 @@ internal open class CancellableContinuationImpl<in T>(
219
229
}
220
230
is CancelledContinuation -> {
221
231
/*
222
- * If continuation was cancelled, then all further resumes must be
223
- * ignored, because cancellation is asynchronous and may race with resume.
224
- * Racy exceptions will be lost, too. There does not see to be a safe way to
225
- * handle them without producing spurious crashes.
226
- *
227
- * :todo: we should somehow remember the attempt to invoke resume and fail on the second attempt.
232
+ * If continuation was cancelled, then resume attempt must be ignored,
233
+ * because cancellation is asynchronous and may race with resume.
234
+ * Racy exceptions will be lost, too.
228
235
*/
229
- return
236
+ if (state.makeResumed()) return // ok -- resumed just once
230
237
}
231
- else -> error(" Already resumed, but proposed with update $proposedUpdate " )
232
238
}
239
+ alreadyResumedError(proposedUpdate) // otherwise -- an error (second resume attempt)
233
240
}
234
241
}
235
242
243
+ private fun alreadyResumedError (proposedUpdate : Any? ) {
244
+ error(" Already resumed, but proposed with update $proposedUpdate " )
245
+ }
246
+
236
247
// Unregister from parent job
237
248
private fun disposeParentHandle () {
238
249
parentHandle?.let { // volatile read parentHandle (once)
0 commit comments