Skip to content

JNI: forward CompletableFuture cancellation to Swift tasks - #10

Merged
teamhimeh merged 1 commit into
qoncept-forkfrom
codex/fix-jni-async-cancellation
Oct 23, 2026
Merged

teamhimeh merged 1 commit into
qoncept-forkfrom
codex/fix-jni-async-cancellation

Conversation

@teamhimeh

Copy link
Copy Markdown
Collaborator

Problem

Cancelling a future returned by a JNI async call currently changes only the Java future's state. The underlying Swift task keeps running. The generated thenApply result-conversion stage also prevents cancellation of the returned future from reaching the source future.

For example:

public func waitForWork() async throws -> Int64 {
  try await Task.sleep(for: .seconds(60))
  return 1
}

With this change, cancelling its generated Java future also cancels the Swift task, so the suspension observes cancellation:

var future = MySwiftLibrary.waitForWork();
future.cancel(false);

Swift cancellation remains cooperative. This does not interrupt an operating-system thread or wait for the task to finish.

Changes

  • Add a SwiftCompletableFuture runtime implementation that owns a retained Swift task handle and cancels/releases it safely on completion or cancellation.
  • Bind each generated JNI async task to its native future, including tasks that complete before the handle is installed.
  • Convert results through a cancellation-forwarding mapping stage, preserving success, failure, and conversion exceptions while keeping the generated public return type as CompletableFuture.
  • Update JNI signatures and generator expectations for the concrete native future type. Keep legacyFuture behavior unchanged.
  • Document cooperative cancellation and the boundary between generated result conversion and future stages added by callers.
  • Add generator regressions, Java future tests, and JNI runtime tests that observe Swift cancellation for scalar and Optional results and exercise immediate task completion.

Validation

  • Confirmed the new generator regressions fail before the fix; the legacy-mode regression passes before and after.
  • swift test --disable-experimental-prebuilts --disable-sandbox: full Swift test suite passed.
  • ./gradlew :SwiftKitCore:test :Samples:SwiftJavaExtractJNISampleApp:test --no-daemon: 22 runtime-library tests and 289 JNI sample tests passed, with no failures or skips.
  • swift-format lint --strict on changed Swift files and git diff --check passed.

Tested with Apple Swift 6.4 and JetBrains Runtime 25 on macOS.

@teamhimeh
teamhimeh merged commit 1f4c682 into qoncept-fork Oct 23, 2026
33 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant