Repository navigation
JNI: preserve MainActor isolation in synchronous thunks (swiftlang#938) - #1
Closed
piercifani wants to merge 3 commits into
Closed
piercifani wants to merge 3 commits into
piercifani wants to merge 3 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
A jextract (JNI mode) sample whose Swift modules build in Swift 6 language mode and whose bindings are exercised from Kotlin tests: one test file per feature under src/test/kotlin. KotlinSampleModels is a separate module, so types (and later protocols) cross module boundaries. The baseline covers what works today: a struct, a throwing function, an async function awaited as a coroutine, an enum with a payload, and a type from another module. Added to the CI sample matrix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Synchronous MainActor APIs currently produce nonisolated JNI entry points, so their generated bindings fail to compile when calling isolated Swift initializers or members. Annotate these entry points with MainActor and check the caller's executor before converting arguments or accessing Swift state. Keep the Java API synchronous, preserve isolation through generic openers, and leave nonisolated members and asynchronous downcalls on their existing paths. Isolating the thunk itself also supports reference-valued JNI results in Swift 6 without requiring JNI pointers to conform to Sendable. Add regression tests, JNI sample compilation fixtures, and documentation of the executor requirement for Java callers. Verify the regressions against the original generator, run the full Swift and JNI sample test suites, and compile the fixtures in Swift 6 language mode.
Kotlin test for synchronous @mainactor bindings. It runs on macOS only, where the JVM test thread can be the process's main thread (-XstartOnFirstThread), as Android's UI thread is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
piercifani
force-pushed
the
jni/01-main-actor-thunks
branch
from
November 3, 2026 14:41
f6c879d to
201c778
Compare
This comment has been minimized.
This comment has been minimized.
Author
|
Superseded by a replacement PR (linked below once opened). The Kotlin sample commits now sit on |
Author
|
Replaced by #11. |
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 1/8 · base:
main· next: #2 (jni/02-closures-jextracted-types)This is upstream swiftlang/swift-java#938 by Himeshi (teamhimeh on GitHub), carried unchanged with its authorship, so the rest of this stack can build on it. It stays here until swiftlang#938 is merged upstream, then this branch is dropped and the stack is rebased onto
main.What it does (from swiftlang#938)
JNI extraction kept synchronous Java signatures for
@MainActorAPIs but emitted nonisolated Swift thunks, so the generated bindings didn't compile when they touched isolated initializers, methods, properties or subscripts.@MainActorwhen the declaration, its enclosing type or its extension is explicitly main-actor isolated.MainActor.preconditionIsolated()before converting arguments. Java callers must already be on the main actor's executor; there's no hop.nonisolatedmembers and async downcalls are unchanged.Why Skip needs it
In Skip Fuse apps every view model is a
@MainActorclass read from Compose on the UI thread. In a production Skip Fuse app (16 Skip modules), 4,180 bridged entrypoints are main-actor isolated, and all 190 view models write@MainActorexplicitly, so swiftlang#938's explicit-attribute detection covers them.Kotlin in this PR
This PR's first commit adds
Samples/SwiftJavaExtractJNIKotlinSampleApp. Its Swift modules build in Swift 6 language mode, and its bindings are used from Kotlin: one test file per feature,kotlin.testpluskotlinx-coroutinesforawait()on the generated futures. It's in the CI sample matrices like the other samples. Each later PR in the stack adds its own Swift fixture and Kotlin test, so every PR shows the Kotlin it enables. (It was briefly a separate PR, #9, but GitHub doesn't allow changing the base of the first PR in a stack.)BaselineTest.kt: what works onmaintoday. A struct, a throwing function, anasyncfunction awaited as a coroutine, an enum with a payload, and a type from the other module.MainActorTest.ktwithMainActorViewModel.swift: a@MainActorview model called synchronously from Kotlin. It runs on macOS only, where the JVM test thread can be the process's main thread (-XstartOnFirstThread), as Android's UI thread is; on Linux the JVM never runs tests on the process's main thread.Extra validation done here
./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:testpasses (the baseline tests also pass onmainwithout this PR).@MainActorview model, called from Kotlin on the JVM, passes when the test JVM runs on the process's first thread (-XstartOnFirstThreadon macOS, which matches Android's UI thread being the process main thread). Off the main thread the JVM stops with SIGTRAP from the precondition, as intended.Note for Skip: today Skip's bridge uses
assumeMainActorUnchecked, which lets off-main calls through silently. With this change they trap, which will surface latent threading bugs in apps.🤖 Generated with Claude Code