@@ -106,7 +106,8 @@ internal open class CancellableContinuationImpl<in T>(
106106 _state .loop { state ->
107107 if (state !is NotCompleted ) return false // false if already complete or cancelling
108108 // 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
110111 // Invoke cancel handler if it was present
111112 if (state is CancelHandler ) invokeHandlerSafely { state.invoke(cause) }
112113 // Complete state update
@@ -177,28 +178,37 @@ internal open class CancellableContinuationImpl<in T>(
177178 val node = handleCache ? : makeHandler(handler).also { handleCache = it }
178179 if (_state .compareAndSet(state, node)) return // quit on cas success
179180 }
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)
183182 is CancelledContinuation -> {
184183 /*
185184 * 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+ / *
191190 * :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension,
192191 * because we play type tricks on Kotlin/JS and handler is not necessarily a function there
193192 */
194193 invokeHandlerSafely { handler.invokeIt((state as ? CompletedExceptionally )?.cause) }
195194 return
196195 }
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+ }
198204 }
199205 }
200206 }
201207
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+
202212 private fun makeHandler (handler : CompletionHandler ): CancelHandler =
203213 if (handler is CancelHandler ) handler else InvokeOnCancel (handler)
204214
@@ -219,20 +229,21 @@ internal open class CancellableContinuationImpl<in T>(
219229 }
220230 is CancelledContinuation -> {
221231 /*
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.
228235 */
229- return
236+ if (state.makeResumed()) return // ok -- resumed just once
230237 }
231- else -> error(" Already resumed, but proposed with update $proposedUpdate " )
232238 }
239+ alreadyResumedError(proposedUpdate) // otherwise -- an error (second resume attempt)
233240 }
234241 }
235242
243+ private fun alreadyResumedError (proposedUpdate : Any? ) {
244+ error(" Already resumed, but proposed with update $proposedUpdate " )
245+ }
246+
236247 // Unregister from parent job
237248 private fun disposeParentHandle () {
238249 parentHandle?.let { // volatile read parentHandle (once)
0 commit comments