Integrate PRs 1469 and 1478 from brunoborges#1477
Conversation
Use a multi-release JAR to select virtual threads as the default internal executor on JDK 25+, while retaining the common pool on older JDKs. Keep user-provided executors caller-owned and only shut down SDK-owned defaults. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Supersedes #1469. Adds an internal multi-release executor provider so the Java SDK automatically uses virtual threads on JDK 25+ and ForkJoinPool.commonPool() on older JDKs, while making CopilotClient consistently route async work through a single resolved executor and shut down only the SDK-owned default.
Changes:
- Introduce package-private
InternalExecutorProviderwith a Java 17 baseline (common pool, unowned) and asrc/main/java25multi-release variant (virtual-thread-per-task, owned). - Wire
CopilotClientto resolveexecutor/ownedExecutoronce in the constructor, use it forstart, RPC dispatch, session creation/resume, andstop, and shut down the owned executor inclose/forceStop. - Update Maven build with a
java25-multi-releaseprofile (JDK ≥ 25) producingMETA-INF/versions/25classes andMulti-Release: true, exclude those from JaCoCo, refineCopilotClientOptionsJavadoc, and addInternalExecutorProviderTest.
Show a summary per file
| File | Description |
|---|---|
| java/src/main/java/com/github/copilot/InternalExecutorProvider.java | Java 17 baseline provider returning ForkJoinPool.commonPool() (unowned). |
| java/src/main/java25/com/github/copilot/InternalExecutorProvider.java | JDK 25 multi-release provider returning an owned virtual-thread executor. |
| java/src/main/java/com/github/copilot/CopilotClient.java | Stores resolved executor/ownedExecutor, uses it everywhere, shuts down owned executor in close/forceStop. |
| java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java | Javadoc clarifies SDK-default executor strategy and user-owned shutdown contract. |
| java/pom.xml | Adds JDK 25 multi-release compile profile, manifest entry, and JaCoCo exclusion for META-INF/versions/**. |
| java/src/test/java/com/github/copilot/InternalExecutorProviderTest.java | Verifies base provider, ownership behavior, and JDK 25 multi-release provider/client shutdown via a temp MR jar. |
Copilot's findings
- Files reviewed: 7/7 changed files
- Comments generated: 2
| if (Runtime.version().feature() < 25) { | ||
| return; | ||
| } |
| if (Runtime.version().feature() < 25) { | ||
| return; | ||
| } |
|
@brunoborges I will pull commits in from your #1478 . I already invested time in the CI approach and I will prioritize that approach with your code changes in 1478. |
|
@brunoborges to give you an idea of what I'm doing, see this prompt: Use the I have checked out the topic branch of #1478 into my worktree, as shown in this output. I intend for Bruno has created his 1478 with this message: I want to keep the JDK 17/JDK 25 approach we evolved here, but pull in the code changes from Bruno's other commit. I'm iterating it here. |
|
Here's a summary of how #1478 differs from #1469: Key Differences
What #1478 improves over #1469
What your branch (#1477/1469) has that #1478 doesn't
Recommendation for cherry-pickingFrom #1478, you'd want to pull in:
While keeping your branch's CI/workflow/enforcer approach intact. Completed: Check and modify Emacs compilation-error-regexp-alist for Maven output (5/5) |
Cherry-pick code changes from brunoborges/java-jdk25-default-executor: - Instance-based InternalExecutorProvider with canBeShutdown() API - SHUTDOWN_DISPATCHER to avoid deadlock in forceStop() - Idempotent shutdownOwnedExecutor() (short-circuits if already shut down) - maven-failsafe-plugin + InternalExecutorProviderIT (spawns child JVM against packaged JAR) - InternalExecutorProviderProbe for IT verification - Antrun verify-java25-overlay (checks actual JAR zip entry in java25-multi-release profile) - Simplified README (no manual executor configuration needed) - Split unit tests into single-assertion methods Co-authored-by: Bruno Borges <bruno@brunoborges.com>
|
Cherry-pick code changes from brunoborges/java-jdk25-default-executor:
Co-authored-by: Bruno Borges bruno@brunoborges.com" Done. All cherry-picked and committed. Let me verify the final state: Ran terminal command: cd /home/edburns/workareas/copilot-sdk && git log --oneline main..HEAD Cherry-picked successfully. Your branch
Completed: Cherry-pick changes from PR 1478 (6/7) |
|
I've added banners to the test so when we run the tests two ways, we see: [WARNING] [echo] ⚠⚠⚠ Using Java 25.0.2 (Microsoft) to run tests. |
…oborges-pr-1469 # Conflicts: # java/src/main/java/com/github/copilot/CopilotClient.java
After merging upstream/main (which introduced the initializeSession lambda with a conditional options.getExecutor() guard), restore PR 1477's intent: always set the resolved executor field on every session. This ensures the JDK 25 virtual-thread default is propagated even when no user-provided executor is supplied. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add title and show-coverage inputs to java-test-report action so the summary clearly identifies which JDK produced each set of results. The JDK 25 section includes coverage; the JDK 17 section shows only test counts.
|
This one has proved too messy. I'll take Bruno's #1478 and apply the CI/CD and pom changes to it. |
Supersedes #1469 .
Superseded by #1483.
Risk Assessment: PR #1469 — feat(java): add JDK 25 default executor
Superseding PR: #1469
Branch:
edburns/review-brunoborges-pr-1469Assessed: 2026-05-27
What This PR Does
Introduces a multi-release JAR (MR-JAR) mechanism so the Java SDK automatically uses
Executors.newVirtualThreadPerTaskExecutor()on JDK 25+ while falling back toForkJoinPool.commonPool()on older JDKs. It also refactorsCopilotClientto always hold a non-nullexecutorfield and properly shut down SDK-owned executors on close.Key Components
src/main/java/.../InternalExecutorProvider.javaForkJoinPool.commonPool(),isOwned() = falsesrc/main/java25/.../InternalExecutorProvider.javanewVirtualThreadPerTaskExecutor(),isOwned() = truepom.xml(profilejava25-multi-release)src/main/java25with--release 25, setsMulti-Release: truemanifestCopilotClient.javaCopilotClientOptions.javaRisk Matrix
multiReleaseOutputis a maven-compiler-plugin 3.12+ feature — The POM uses 3.15.0 which supports it, but this is a relatively new Maven feature with limited community battle-testing.shutdownOwnedExecutor()is called in bothforceStop()(viawhenComplete) andclose()(infinally). Double-call is safe (shutdown()is idempotent), but theawaitTerminationinclose()may block the calling thread up to 10 s afterforceStopalready ran.shutdownOnceAtomicBoolean for clarity but not required.ForkJoinPool.commonPool()is not owned — virtual-thread executor IS owned — The asymmetry is intentional but surprising. If someone later refactors and swaps implementations they must preserve this contract or risk shutting down the common pool.isOwned()semantics; consider adding a comment in the base class explaining why it returns false.ownedExecutorprivate field — Fragile coupling to internals. If the field is renamed or the executor strategy changes, the test breaks silently.META-INF/versions/**/*.classfrom coverage. The JDK-25 code path is tested only by the reflection-based test, which itself is skipped on JDK < 25.CompletableFuture.supplyAsync(fn)(uses common pool). Now it always passes an explicit executor. Behavior is identical on JDK < 25 but changes on JDK 25+.Overall Assessment
Verdict: Acceptable risk for GA provided:
Recommendations
Consider unconditional cross-compilation — Remove the
<jdk>[25,)</jdk>activation and always compile the java25 sources with--release 25(Maven will skip if the toolchain can't satisfy it, but a CI job with JDK 25 will always produce the overlay). This avoids the "built on wrong JDK" silent failure.Add a build-time JAR verification — A Maven Enforcer or maven-jar-plugin check that confirms
META-INF/versions/25/com/github/copilot/InternalExecutorProvider.classexists in the final artifact when built on JDK 25+.Document the two-source-tree requirement — Add a brief note in
java/README.mdor aCONTRIBUTING.mdsection explaining that changes toInternalExecutorProvidermust be mirrored in bothsrc/main/java/andsrc/main/java25/.Consider a
@sincetag — Mark the virtual-thread behavior with@since 1.xin Javadoc for SDK consumers who want to understand when this appeared.