Propagate Swift task cancellation to bridged Kotlin async throws calls - #281
Open
piercifani wants to merge 1 commit into
Open
piercifani wants to merge 1 commit into
piercifani wants to merge 1 commit into
Conversation
The generated Swift for a bridged `async throws` API awaited the Kotlin `callback_` function with a plain continuation, and the Kotlin side ran the call in a fire-and-forget `Task`, so cancelling the Swift task never reached the coroutine: a Kotlin implementation parked in `suspendCancellableCoroutine` ran to completion (or to its own timeout) before the await resumed. The Kotlin `callback_` function now runs the call under its own `kotlinx.coroutines.Job` and returns it; the Swift side awaits inside `withTaskCancellationHandler`, attaches the job through the new `SkipBridge.BridgedJob` helper and cancels it when the task is cancelled. A throwable delivered after that cancellation surfaces as `CancellationError`. Cancelling the job before the call starts makes `withContext` throw, so the callback still fires exactly once. Non-throwing async APIs are unchanged, as Swift cancellation can only surface through a throwing call. Requires skip-bridge with `BridgedJob`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
piercifani
force-pushed
the
async-cancellation
branch
from
September 24, 2026 09:10
31fa90e to
81446a3
Compare
Contributor
Author
|
The |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cancelling a Swift task that awaits a bridged Kotlin
async throwsAPI never reached the Kotlin coroutine. The generated Swift awaited thecallback_function with a plainwithCheckedThrowingContinuation, and the generated Kotlin ran the call in a fire-and-forgetTask { … }, so a Kotlin implementation parked insuspendCancellableCoroutine(an OkHttp call, a channel receive) ran to completion or to its own timeout before the Swift await resumed.invokeOnCancellationnever fired.This change makes the two sides cooperate:
callback_for throwing async functions and properties) runs the call under its ownkotlinx.coroutines.JobviawithContext(f_job)and returns that job. Cancelling it before the call starts makeswithContextthrow, so the callback still fires exactly once.withTaskCancellationHandler, attaches the returned job through the newSkipBridge.BridgedJobhelper (Add BridgedJob so cancelling a Swift task cancels the bridged Kotlin coroutine skip-bridge#120) and cancels it from the handler. A throwable delivered after that cancellation surfaces asCancellationError.callback_functions changes from…)Vto…)Lkotlinx/coroutines/Job;; both sides are generated together.Non-throwing async APIs are unchanged: Swift cancellation can only surface through a throwing call, and
withCheckedContinuationhas no way to report it.skip.lib.Task.cancel()is deliberately not used for this: it only flips the cooperative flag and runs Swift-style cancellation handlers, it does not cancel the underlyingDeferred, so a coroutine suspended insuspendCancellableCoroutinewould not observe it.Testing
SkipSyntaxTests867/867: theasync throwsgolden cases inBridgeToSwiftTestsandBridgeToKotlinTestsupdated to the new shape, andBridgeSendabilityTests.testGeneratedContinuationCompilesWithoutWarningsnow compiles the whole generated function (with aBridgedJobstub) under-warnings-as-errors.skipbuilt from this branch driving code generation and Add BridgedJob so cancelling a Swift task cancels the bridged Kotlin coroutine skip-bridge#120 providingBridgedJoband the new sample test: skip-bridge's Robolectric run (SkipBridgeToSwiftSamplesTestsSupportTests.XCSkipTests) cancels a Swift task awaiting a Kotlin function parked insuspendCancellableCoroutine. With the released 1.9.11 generator that test fails ("The await did not resume within 5 s of the cancellation"); with this branch the await resumes withCancellationErrorand the coroutine'sinvokeOnCancellationfires, 70/70 pass. Cancelling before the call starts also resumes.Depends on skiptools/skip-bridge#120 for
BridgedJob.🤖 Generated with Claude Code