Repository navigation
JNI: async closures implemented in Java return a CompletableFuture - #22
Open
piercifani wants to merge 3 commits into
Open
piercifani wants to merge 3 commits into
piercifani wants to merge 3 commits into
Conversation
An async closure parameter got a synchronous Java interface: Swift called it and continued without waiting for the Java side. Its functional interface now returns `CompletableFuture<T>` (boxed, `Void` for no result), and the Swift closure awaits it with `_awaitCompletableFuture`, which resumes the task when the future completes instead of blocking a thread. It's the same mechanism as `async` requirements of Java-implemented protocols: `apply` is called dynamically, because wrap-java doesn't bind a generic `CompletableFuture<T>` result, and the value is converted on the thread the task resumed on. A failed future throws in a throwing closure and traps otherwise. Results: `Void`, types translated directly and jextracted types. Async closures never use the non-escaping lowering or a JDK functional interface, and need the `completableFuture` async mode. The Java API matches swiftlang#913, which awaits with a blocking `get()`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 3, 2026
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.
Stack 18/21 · base:
jni/17-closure-results· previous: #21 · next: #23 (jni/19-enum-swift-case-names)Problem
An
asyncclosure parameter got a synchronous Java interface,void apply(Item). Swift called it and went on without waiting for the Java side, so Java couldn't do asynchronous work for an async closure. Kotlin couldn't either: a coroutine, or aCompletableFuture, returned from the lambda was ignored.Changes
An async closure's functional interface returns
CompletableFuture<T>, withTboxed andVoidfor no result:The Swift closure calls it and awaits the future with
_awaitCompletableFuture, from #7. That resumes the task when the future completes instead of blocking a thread. It's the same mechanism as anasyncrequirement of a Java-implemented protocol:applyis called dynamically, because wrap-java doesn't bind a genericCompletableFuture<T>result.Void, types translated directly (primitives,String) and jextracted types.(Int64) async -> Int64isn't aLongUnaryOperator.completableFutureasync mode and are skipped inlegacyFuturemode, since aFuturecan't tell Swift it completed.Relation to swiftlang#913
Upstream swiftlang#913 gives async closures the same Java API. This PR implements it separately, on top of this stack:
future.get(), which blocks the Swift task's thread until Java completes. This PR awaits without blocking, with the continuation from JNI: Java implementations of async and cross-module Swift protocols #7.When swiftlang#913 lands upstream, the two need reconciling.
Kotlin in this PR
ClosuresTest.ktwithClosures.swift:Why Skip needs it
Skip bridges async Swift closures to Kotlin suspend functions, so a Kotlin implementation can suspend and Swift waits for it.
Testing
./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:testpasses (46 tests).JNIAsyncClosureTests(4 tests):legacyFuturemode skips them.AsyncClosuresTestchecks four cases:Voidfuture;swift test --disable-experimental-prebuilts --disable-sandbox(686 tests),./gradlew :Samples:SwiftJavaExtractJNISampleApp:test(302 tests) and./gradlew :SwiftKitCore:testpass.swift format formatleaves changed files unchanged;swift format lint --strict;git diff --check.Not in this PR
🤖 Generated with Claude Code