[Java] Add in-process FFI runtime host lifecycle and stream transport primitives - #2233
Conversation
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Adds Java in-process FFI lifecycle management and stream-based JSON-RPC transport primitives.
Changes:
- Adds FFI host startup, callback handling, I/O, and shutdown.
- Adds queue-backed streams and MR-JAR thread factories.
- Adds focused lifecycle, concurrency, and stream tests.
Show a summary per file
| File | Description |
|---|---|
java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java |
Adds stream-based client construction. |
java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java |
Manages native runtime lifecycle. |
java/sdk/src/main/java/com/github/copilot/ffi/FfiOutputStream.java |
Bridges writes to native connections. |
java/sdk/src/main/java/com/github/copilot/ffi/QueueInputStream.java |
Queues native callback data for reading. |
java/sdk/src/main/java/com/github/copilot/ffi/ReaderThreadFactory.java |
Provides the JDK 17 thread implementation. |
java/sdk/src/main/java25/com/github/copilot/ffi/ReaderThreadFactory.java |
Provides the JDK 25 virtual-thread overlay. |
java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java |
Tests lifecycle, callbacks, and concurrency. |
java/sdk/src/test/java/com/github/copilot/ffi/QueueInputStreamTest.java |
Tests queue-backed stream behavior. |
Review details
Suppressed comments (3)
java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java:123
- Rollback only handles a returned zero. If
connectionOpenthrows (for example, a JNA invocation failure),start()exits whileserverIdandcallbackRefremain live andhostShutdownis never called, leaking the native worker. Apply the same best-effort rollback for every unsuccessful open, including exceptions.
OutboundCallback callback = createOutboundCallback();
callbackRef = callback;
int connHandle = nativeBinding.connectionOpen(hostHandle, callback, Pointer.NULL, null, 0, null, 0, null, 0);
if (connHandle == 0) {
try {
java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java:232
ReaderThreadFactoryis only used forhost_starthere, so the JDK 25 overlay virtualizes the startup call rather than theQueueInputStreamconsumer. The actual consumer still comes from the hard-coded platform thread inJsonRpcClient.java:60-64, so the promised MR-JAR reader swap never takes effect. Wire this factory into JSON-RPC reader creation and use a separate blocking executor forhost_start.
ReaderThreadFactory readerThreadFactory = new ReaderThreadFactory();
ExecutorService executor = Executors
.newSingleThreadExecutor(runnable -> readerThreadFactory.create(runnable, "copilot-ffi-host-start"));
java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java:197
- An interrupt currently abandons the drain, after which
close()callshostShutdownand releases the callback reference even though callbacks are still active. That can free native callback state while Java is still using it. Drain uninterruptibly and restore the interrupt status only after the active count reaches zero.
try {
callbackDrainMonitor.wait(10L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
- Files reviewed: 8/8 changed files
- Comments generated: 3
- Review effort level: Balanced
|
Can you do it for me?
…On Mon, Aug 3, 2026, 1:31 PM Copilot ***@***.***> wrote:
***@***.**** commented on this pull request.
Pull request overview
Adds Java in-process FFI lifecycle management and stream-based JSON-RPC
transport primitives.
*Changes:*
- Adds FFI host startup, callback handling, I/O, and shutdown.
- Adds queue-backed streams and MR-JAR thread factories.
- Adds focused lifecycle, concurrency, and stream tests.
Show a summary per file
File Description
java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java Adds
stream-based client construction.
java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java Manages
native runtime lifecycle.
java/sdk/src/main/java/com/github/copilot/ffi/FfiOutputStream.java Bridges
writes to native connections.
java/sdk/src/main/java/com/github/copilot/ffi/QueueInputStream.java Queues
native callback data for reading.
java/sdk/src/main/java/com/github/copilot/ffi/ReaderThreadFactory.java Provides
the JDK 17 thread implementation.
java/sdk/src/main/java25/com/github/copilot/ffi/ReaderThreadFactory.java Provides
the JDK 25 virtual-thread overlay.
java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java Tests
lifecycle, callbacks, and concurrency.
java/sdk/src/test/java/com/github/copilot/ffi/QueueInputStreamTest.java Tests
queue-backed stream behavior. Review details Suppressed comments (3)
*java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java:123*
- Rollback only handles a returned zero. If connectionOpen throws (for
example, a JNA invocation failure), start() exits while serverId and
callbackRef remain live and hostShutdown is never called, leaking the
native worker. Apply the same best-effort rollback for every unsuccessful
open, including exceptions.
OutboundCallback callback = createOutboundCallback();
callbackRef = callback;
int connHandle = nativeBinding.connectionOpen(hostHandle, callback, Pointer.NULL, null, 0, null, 0, null, 0);
if (connHandle == 0) {
try {
*java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java:232*
- ReaderThreadFactory is only used for host_start here, so the JDK 25
overlay virtualizes the startup call rather than the QueueInputStream
consumer. The actual consumer still comes from the hard-coded platform
thread in JsonRpcClient.java:60-64, so the promised MR-JAR reader swap
never takes effect. Wire this factory into JSON-RPC reader creation and use
a separate blocking executor for host_start.
ReaderThreadFactory readerThreadFactory = new ReaderThreadFactory();
ExecutorService executor = Executors
.newSingleThreadExecutor(runnable -> readerThreadFactory.create(runnable, "copilot-ffi-host-start"));
*java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java:197*
- An interrupt currently abandons the drain, after which close() calls
hostShutdown and releases the callback reference even though callbacks
are still active. That can free native callback state while Java is still
using it. Drain uninterruptibly and restore the interrupt status only after
the active count reaches zero.
try {
callbackDrainMonitor.wait(10L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
- *Files reviewed:* 8/8 changed files
- *Comments generated:* 3
- *Review effort level:* Balanced
------------------------------
In java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java
<#2233 (comment)>:
> + byte[] argvJson = buildArgvJson(entrypointPath, options);
+ byte[] envJson = buildEnvJson(options);
+ int hostHandle = runHostStartOnBlockingThread(argvJson, envJson);
+ if (hostHandle == 0) {
hostStart can block for about 30 seconds, but close() may run during this
wait, observe both handles as zero, mark the host disposed, and return.
This method then publishes new native handles after teardown has already
completed; subsequent close() is a no-op, leaking the host and
connection. Serialize startup with close, or recheck disposal and tear down
any acquired handles before publishing them.
This issue also appears in the following locations of the same file:
- line 119
- line 193
- line 230
------------------------------
In java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java
<#2233 (comment)>:
> + @test
+ void startWithSpikeLibrarySupportsLifecycleAndDataFlow() throws Exception {
+ assumeTrue(testLibExists(), "Native test library not found at " + SPIKE_LIB_PATH);
+ CallbackTestLib callbackTestLib = loadTestLib();
This native integration test is skipped in a clean checkout: the
referenced target/release/libcallback_test.so is ignored/not committed,
and the Maven build does not build the Rust crate or set
copilot.test.nativelib.path. As a result, the only test that exercises
the real JNA callback/lifecycle path never runs in normal Java CI. Build
the platform-specific test library as part of the test setup and fail when
it is unavailable on supported CI platforms.
------------------------------
In java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java
<#2233 (comment)>:
> + /**
+ * Creates a JSON-RPC client over arbitrary input/output streams.
+ */
+ public static JsonRpcClient fromStreams(InputStream inputStream, OutputStream outputStream) {
+ return new JsonRpcClient(inputStream, outputStream, null, null);
A client created by this factory does not own a socket or process, and
JsonRpcClient.close() currently closes neither inputStream nor
outputStream; it only shuts down the executor and closes a
socket/process. Thus closing a stream-backed client leaves both supplied
resources open, contrary to the planned fromStreams cleanup behavior.
Define ownership explicitly and close the supplied streams for this
construction path.
—
Reply to this email directly, view it on GitHub
<#2233?email_source=notifications&email_token=CKNADEINB4Q6WOXXCUIA2Y35IDK6XA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIOBUG42DCMBRHE42M4TFMFZW63VKON2WE43DOJUWEZLEUVSXMZLOOSWGM33PORSXEX3DNRUWG2Y#pullrequestreview-4847410199>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/CKNADEK7AI4VOYML5LGP2W35IDK6XAVCNFSNUABGKJSXA33TNF2G64TZHMYTCMZTHA4DGOBVGA5US43TOVSTWNJQGUZDMMBUHEZTHILWAI>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/CKNADEIMEHLAK3R6TQBE5KL5IDK6XA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIOBUG42DCMBRHE42M4TFMFZW63VKON2WE43DOJUWEZLEUVSXMZLOOSVGM33PORSXEX3JN5ZQ>
and Android
<https://github.com/notifications/mobile/android/CKNADEKRKL5CZ6JJVRTI6PL5IDK6XA5CNFSNUABKM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UKJSXM2LFO4XTIOBUG42DCMBRHE42M4TFMFZW63VKON2WE43DOJUWEZLEUVSXMZLOOSXGM33PORSXEX3BNZSHE33JMQ>.
Download it today!
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
1. FfiRuntimeHost: serialize start() with close() via operationLock to prevent race where close() completes while hostStart blocks, then start() publishes handles after teardown. 2. FfiRuntimeHostTest: add Javadoc explaining the native integration test is intentionally skipped in CI and how to run it locally. 3. JsonRpcClient.fromStreams(): take ownership of supplied streams and close them in close() to prevent resource leaks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cross-SDK Consistency Review ✅This PR improves cross-SDK consistency by bringing Java up to parity with all other SDK implementations. Summary of findings: The
The No consistency gaps identified. This PR closes the Java parity gap on in-process FFI hosting.
|
cc0084e
into
edburns/1917-java-embed-rust-cli-runtime-dd-3039924-agentic-run-02
This PR implements task 4.5 of the Java embedded runtime plan: a dedicated
FfiRuntimeHostthat owns the full in-process FFI lifecycle and bridges runtime I/O through Java streams compatible withJsonRpcClient. It also adds the MR-JAR thread-factory swap point and focused tests for callback, shutdown, and concurrency behavior.FFI runtime host lifecycle (
com.github.copilot.ffi)FfiRuntimeHostto manage:host_start(on a dedicated blocking thread)connection_openwith outbound callback registrationclosingflag →connection_close→ callback drain →host_shutdownAutoCloseablewith best-effort teardown and non-throwingclose().Throwablecatch +WARNINGlogging) and callback exception handler registration.Transport stream bridge
QueueInputStream(BlockingQueue<byte[]>-backed) for native callback →InputStreamdelivery.FfiOutputStreamforOutputStream→connection_write, including write/close race protection via shared operation lock and closed-state checks.MR-JAR reader thread factory
ReaderThreadFactory(src/main/java/.../ffi) using daemon platform threads.ReaderThreadFactory(src/main/java25/.../ffi) usingThread.ofVirtual().JSON-RPC integration seam
JsonRpcClient.fromStreams(InputStream, OutputStream)factory so in-process transport can reuse existing JSON-RPC framing/reader logic.Focused tests
QueueInputStreamTestfor chunked read semantics, blocking behavior, and EOF/close behavior.FfiRuntimeHostTestfor lifecycle, callback delivery, shutdown drain behavior, callback exception containment, and write/close concurrency safety (including spike test library integration path).