From b9ad1c64a7dbe102b879aa6d1acc887bdb84a7b9 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 27 May 2026 15:15:16 -0400 Subject: [PATCH 1/4] feat(java): add JDK 25 default executor 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> --- java/README.md | 12 +- java/pom.xml | 46 +++++++ .../com/github/copilot/CopilotClient.java | 39 +++--- .../copilot/DefaultExecutorProvider.java | 22 ++++ .../copilot/rpc/CopilotClientOptions.java | 21 +-- .../copilot/DefaultExecutorProvider.java | 23 ++++ .../copilot/DefaultExecutorProviderTest.java | 124 ++++++++++++++++++ 7 files changed, 252 insertions(+), 35 deletions(-) create mode 100644 java/src/main/java/com/github/copilot/DefaultExecutorProvider.java create mode 100644 java/src/main/java25/com/github/copilot/DefaultExecutorProvider.java create mode 100644 java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java diff --git a/java/README.md b/java/README.md index b50bc0124..5cf55f06a 100644 --- a/java/README.md +++ b/java/README.md @@ -24,7 +24,7 @@ Java SDK for programmatic control of GitHub Copilot CLI, enabling you to build A ### Requirements -- Java 17 or later. **JDK 25 recommended**. Selecting JDK 25 enables the use of virtual threads, as shown in the [Quick Start](#quick-start). +- Java 17 or later. **JDK 25 recommended**. On JDK 25 and later, the SDK automatically uses virtual threads for its default internal executor. - GitHub Copilot CLI 1.0.17 or later installed and in `PATH` (or provide custom `cliPath`) ### Maven @@ -69,23 +69,16 @@ implementation 'com.github:copilot-sdk-java:1.0.0-beta-java.4' import com.github.copilot.CopilotClient; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionUsageInfoEvent; -import com.github.copilot.rpc.CopilotClientOptions; import com.github.copilot.rpc.MessageOptions; import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.SessionConfig; -import java.util.concurrent.Executors; - public class CopilotSDK { public static void main(String[] args) throws Exception { var lastMessage = new String[]{null}; // Create and start client - try (var client = new CopilotClient()) { // JDK 25+: comment out this line - // JDK 25+: uncomment the following 3 lines for virtual thread support - // var options = new CopilotClientOptions() - // .setExecutor(Executors.newVirtualThreadPerTaskExecutor()); - // try (var client = new CopilotClient(options)) { + try (var client = new CopilotClient()) { client.start().get(); // Create a session @@ -212,4 +205,3 @@ MIT — see [LICENSE](LICENSE) for details. [![Star History Chart](https://api.star-history.com/svg?repos=github/copilot-sdk-java&type=Date)](https://www.star-history.com/#github/copilot-sdk-java&Date) ⭐ Drop a star if you find this useful! - diff --git a/java/pom.xml b/java/pom.xml index 21628db7e..4d3a3e1d1 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -447,6 +447,10 @@ ${project.build.directory}/jacoco-test-results/sdk-tests.exec ${project.reporting.outputDirectory}/jacoco-coverage + + + META-INF/versions/**/*.class + @@ -507,6 +511,48 @@ -XX:+EnableDynamicAgentLoading + + java25-multi-release + + [25,) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile-java25 + compile + + compile + + + 25 + false + + ${project.basedir}/src/main/java25 + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + + + + + skip-test-harness diff --git a/java/src/main/java/com/github/copilot/CopilotClient.java b/java/src/main/java/com/github/copilot/CopilotClient.java index 20c7a7e7d..8feb388d7 100644 --- a/java/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/src/main/java/com/github/copilot/CopilotClient.java @@ -14,6 +14,7 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -78,6 +79,8 @@ public final class CopilotClient implements AutoCloseable { public static final int AUTOCLOSEABLE_TIMEOUT_SECONDS = 10; private static final int FORCE_KILL_TIMEOUT_SECONDS = 10; private final CopilotClientOptions options; + private final Executor executor; + private final ExecutorService ownedExecutor; private final CliServerManager serverManager; private final LifecycleEventManager lifecycleManager = new LifecycleEventManager(); private final Map sessions = new ConcurrentHashMap<>(); @@ -153,6 +156,11 @@ public CopilotClient(CopilotClientOptions options) { this.optionsPort = null; } + Executor providedExecutor = this.options.getExecutor(); + this.executor = providedExecutor != null ? providedExecutor : DefaultExecutorProvider.create(); + this.ownedExecutor = providedExecutor == null && DefaultExecutorProvider.isOwned(this.executor) + && this.executor instanceof ExecutorService executorService ? executorService : null; + this.serverManager = new CliServerManager(this.options); this.serverManager.setConnectionToken(this.effectiveConnectionToken); } @@ -176,11 +184,8 @@ public CompletableFuture start() { private CompletableFuture startCore() { LOG.fine("Starting Copilot client"); - Executor exec = options.getExecutor(); try { - return exec != null - ? CompletableFuture.supplyAsync(this::startCoreBody, exec) - : CompletableFuture.supplyAsync(this::startCoreBody); + return CompletableFuture.supplyAsync(this::startCoreBody, executor); } catch (RejectedExecutionException e) { return CompletableFuture.failedFuture(e); } @@ -209,8 +214,7 @@ private Connection startCoreBody() { Connection connection = new Connection(rpc, process, new ServerRpc(rpc::invoke)); // Register handlers for server-to-client calls - RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, - options.getExecutor()); + RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, executor); dispatcher.registerHandlers(rpc); // Verify protocol version @@ -308,7 +312,6 @@ private static boolean isUnsupportedConnectMethod(JsonRpcException ex) { */ public CompletableFuture stop() { var closeFutures = new ArrayList>(); - Executor exec = options.getExecutor(); for (CopilotSession session : new ArrayList<>(sessions.values())) { Runnable closeTask = () -> { @@ -320,9 +323,7 @@ public CompletableFuture stop() { }; CompletableFuture future; try { - future = exec != null - ? CompletableFuture.runAsync(closeTask, exec) - : CompletableFuture.runAsync(closeTask); + future = CompletableFuture.runAsync(closeTask, executor); } catch (RejectedExecutionException e) { LOG.log(Level.WARNING, "Executor rejected session close task; closing inline", e); closeTask.run(); @@ -344,7 +345,7 @@ public CompletableFuture stop() { public CompletableFuture forceStop() { disposed = true; sessions.clear(); - return cleanupConnection(); + return cleanupConnection().whenComplete((ignored, error) -> shutdownOwnedExecutor()); } private CompletableFuture cleanupConnection() { @@ -436,9 +437,7 @@ public CompletableFuture createSession(SessionConfig config) { long setupNanos = System.nanoTime(); var session = new CopilotSession(sessionId, connection.rpc); - if (options.getExecutor() != null) { - session.setExecutor(options.getExecutor()); - } + session.setExecutor(executor); SessionRequestBuilder.configureSession(session, config); sessions.put(sessionId, session); LoggingHelpers.logTiming(LOG, Level.FINE, @@ -524,9 +523,7 @@ public CompletableFuture resumeSession(String sessionId, ResumeS // Register the session before the RPC call to avoid missing early events. long setupNanos = System.nanoTime(); var session = new CopilotSession(sessionId, connection.rpc); - if (options.getExecutor() != null) { - session.setExecutor(options.getExecutor()); - } + session.setExecutor(executor); SessionRequestBuilder.configureSession(session, config); sessions.put(sessionId, session); LoggingHelpers.logTiming(LOG, Level.FINE, @@ -923,6 +920,14 @@ public void close() { stop().get(AUTOCLOSEABLE_TIMEOUT_SECONDS, TimeUnit.SECONDS); } catch (Exception e) { LOG.log(Level.FINE, "Error during close", e); + } finally { + shutdownOwnedExecutor(); + } + } + + private void shutdownOwnedExecutor() { + if (ownedExecutor != null) { + ownedExecutor.shutdown(); } } diff --git a/java/src/main/java/com/github/copilot/DefaultExecutorProvider.java b/java/src/main/java/com/github/copilot/DefaultExecutorProvider.java new file mode 100644 index 000000000..6967cdeb9 --- /dev/null +++ b/java/src/main/java/com/github/copilot/DefaultExecutorProvider.java @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; + +final class DefaultExecutorProvider { + + private DefaultExecutorProvider() { + } + + static Executor create() { + return ForkJoinPool.commonPool(); + } + + static boolean isOwned(Executor executor) { + return false; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java b/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java index cb372d914..3d1ca15d5 100644 --- a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java +++ b/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java @@ -287,9 +287,11 @@ public CopilotClientOptions setEnvironment(Map environment) { /** * Gets the executor used for internal asynchronous operations. + *

+ * Returns {@code null} if no executor has been explicitly set, indicating that + * the SDK should use its default executor strategy. * - * @return the executor, or {@code null} to use the default - * {@code ForkJoinPool.commonPool()} + * @return the executor, or {@code null} if using SDK defaults */ public Executor getExecutor() { return executor; @@ -299,15 +301,18 @@ public Executor getExecutor() { * Sets the executor used for internal asynchronous operations. *

* When provided, the SDK uses this executor for all internal - * {@code CompletableFuture} combinators instead of the default - * {@code ForkJoinPool.commonPool()}. This allows callers to isolate SDK work - * onto a dedicated thread pool or integrate with container-managed threading. + * {@code CompletableFuture} combinators. This allows callers to isolate SDK + * work onto a dedicated thread pool or integrate with container-managed + * threading. *

- * Passing {@code null} reverts to the default {@code ForkJoinPool.commonPool()} - * behavior. + * The SDK will not shut down a user-provided executor. If you pass a custom + * {@code ExecutorService}, you remain responsible for shutting it down. + *

+ * If not set (or set to {@code null}), the SDK uses its default executor: + * virtual threads on JDK 25+, {@code ForkJoinPool.commonPool()} on older JDKs. * * @param executor - * the executor to use, or {@code null} for the default + * the executor to use, or {@code null} for SDK defaults * @return this options instance for fluent chaining */ public CopilotClientOptions setExecutor(Executor executor) { diff --git a/java/src/main/java25/com/github/copilot/DefaultExecutorProvider.java b/java/src/main/java25/com/github/copilot/DefaultExecutorProvider.java new file mode 100644 index 000000000..a0a4740bb --- /dev/null +++ b/java/src/main/java25/com/github/copilot/DefaultExecutorProvider.java @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +final class DefaultExecutorProvider { + + private DefaultExecutorProvider() { + } + + static Executor create() { + return Executors.newVirtualThreadPerTaskExecutor(); + } + + static boolean isOwned(Executor executor) { + return executor instanceof ExecutorService; + } +} diff --git a/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java b/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java new file mode 100644 index 000000000..a0702e46a --- /dev/null +++ b/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.rpc.CopilotClientOptions; + +class DefaultExecutorProviderTest { + + @Test + void baseProviderUsesCommonPoolWithoutOwnership() { + Executor executor = DefaultExecutorProvider.create(); + + assertSame(ForkJoinPool.commonPool(), executor); + assertFalse(DefaultExecutorProvider.isOwned(executor)); + } + + @Test + void clientDoesNotShutDownUserProvidedExecutor() { + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + try (var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false).setExecutor(executor))) { + assertNotNull(client); + } + + assertFalse(executor.isShutdown()); + } finally { + executor.shutdownNow(); + } + } + + @Test + void multiReleaseJarUsesOwnedVirtualThreadExecutorOnJdk25() throws Exception { + if (Runtime.version().feature() < 25) { + return; + } + + Path classes = Path.of("target", "classes"); + Path baseClass = classes.resolve("com/github/copilot/DefaultExecutorProvider.class"); + Path java25Class = classes.resolve("META-INF/versions/25/com/github/copilot/DefaultExecutorProvider.class"); + assertTrue(Files.exists(baseClass), "Base DefaultExecutorProvider class must be compiled"); + assertTrue(Files.exists(java25Class), "JDK 25 build must compile the multi-release executor provider"); + + Path jar = Files.createTempFile("copilot-sdk-default-executor", ".jar"); + try { + createProviderJar(jar, baseClass, java25Class); + + try (var loader = new URLClassLoader(new URL[]{jar.toUri().toURL()}, null)) { + Class provider = Class.forName("com.github.copilot.DefaultExecutorProvider", true, loader); + Method create = provider.getDeclaredMethod("create"); + Method isOwned = provider.getDeclaredMethod("isOwned", Executor.class); + create.setAccessible(true); + isOwned.setAccessible(true); + + Executor executor = (Executor) create.invoke(null); + try { + assertTrue((Boolean) isOwned.invoke(null, executor)); + CompletableFuture virtualThreadUsed = new CompletableFuture<>(); + executor.execute(() -> virtualThreadUsed.complete(isCurrentThreadVirtual())); + + assertTrue(virtualThreadUsed.get(5, TimeUnit.SECONDS)); + } finally { + if (executor instanceof ExecutorService executorService) { + executorService.shutdownNow(); + } + } + } + } finally { + Files.deleteIfExists(jar); + } + } + + private static boolean isCurrentThreadVirtual() { + try { + Method isVirtual = Thread.class.getMethod("isVirtual"); + return (Boolean) isVirtual.invoke(Thread.currentThread()); + } catch (ReflectiveOperationException e) { + return false; + } + } + + private static void createProviderJar(Path jar, Path baseClass, Path java25Class) throws IOException { + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + attributes.putValue("Multi-Release", "true"); + + try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar), manifest)) { + addClass(output, "com/github/copilot/DefaultExecutorProvider.class", baseClass); + addClass(output, "META-INF/versions/25/com/github/copilot/DefaultExecutorProvider.class", java25Class); + } + } + + private static void addClass(JarOutputStream output, String entryName, Path classFile) throws IOException { + output.putNextEntry(new JarEntry(entryName)); + Files.copy(classFile, output); + output.closeEntry(); + } +} From 349a419fb9e0896057b9333a4f88864449e34e6b Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 27 May 2026 15:23:06 -0400 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../java/com/github/copilot/CopilotClient.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/java/src/main/java/com/github/copilot/CopilotClient.java b/java/src/main/java/com/github/copilot/CopilotClient.java index 8feb388d7..fa9d00476 100644 --- a/java/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/src/main/java/com/github/copilot/CopilotClient.java @@ -926,8 +926,21 @@ public void close() { } private void shutdownOwnedExecutor() { - if (ownedExecutor != null) { - ownedExecutor.shutdown(); + if (ownedExecutor == null) { + return; + } + + ownedExecutor.shutdown(); + try { + if (!ownedExecutor.awaitTermination(AUTOCLOSEABLE_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + LOG.log(Level.FINE, "Owned executor did not terminate within {0} seconds; forcing shutdown.", + AUTOCLOSEABLE_TIMEOUT_SECONDS); + ownedExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + ownedExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + LOG.log(Level.FINE, "Interrupted while waiting for owned executor to terminate", e); } } From ed4a7b9699530f92ae19b84cc571647c3378cdea Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 27 May 2026 15:29:39 -0400 Subject: [PATCH 3/4] test(java): cover owned default executor shutdown Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../copilot/DefaultExecutorProviderTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java b/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java index a0702e46a..65365809b 100644 --- a/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java +++ b/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.lang.reflect.Method; +import java.lang.reflect.Field; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; @@ -95,6 +96,38 @@ void multiReleaseJarUsesOwnedVirtualThreadExecutorOnJdk25() throws Exception { } } + @Test + void clientCloseShutsDownOwnedDefaultExecutorOnJdk25() throws Exception { + if (Runtime.version().feature() < 25) { + return; + } + + Path classes = Path.of("target", "classes"); + Path jar = Files.createTempFile("copilot-sdk-client-default-executor", ".jar"); + try { + createClassesJar(jar, classes); + + try (var loader = new URLClassLoader(new URL[]{jar.toUri().toURL()}, null)) { + Class clientClass = Class.forName("com.github.copilot.CopilotClient", true, loader); + AutoCloseable client = (AutoCloseable) clientClass.getConstructor().newInstance(); + Field ownedExecutorField = clientClass.getDeclaredField("ownedExecutor"); + ownedExecutorField.setAccessible(true); + ExecutorService ownedExecutor = (ExecutorService) ownedExecutorField.get(client); + + assertNotNull(ownedExecutor); + assertFalse(ownedExecutor.isShutdown()); + + client.close(); + + assertTrue(ownedExecutor.isShutdown()); + assertTrue(ownedExecutor.awaitTermination(5, TimeUnit.SECONDS)); + assertTrue(ownedExecutor.isTerminated()); + } + } finally { + Files.deleteIfExists(jar); + } + } + private static boolean isCurrentThreadVirtual() { try { Method isVirtual = Thread.class.getMethod("isVirtual"); @@ -116,6 +149,31 @@ private static void createProviderJar(Path jar, Path baseClass, Path java25Class } } + private static void createClassesJar(Path jar, Path classes) throws IOException { + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + attributes.putValue("Multi-Release", "true"); + + try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar), manifest); + var files = Files.walk(classes)) { + var iterator = files.iterator(); + while (iterator.hasNext()) { + Path file = iterator.next(); + if (!Files.isRegularFile(file)) { + continue; + } + + String entryName = classes.relativize(file).toString().replace('\\', '/'); + if ("META-INF/MANIFEST.MF".equals(entryName)) { + continue; + } + + addClass(output, entryName, file); + } + } + } + private static void addClass(JarOutputStream output, String entryName, Path classFile) throws IOException { output.putNextEntry(new JarEntry(entryName)); Files.copy(classFile, output); From cab403a6536d04a5ef32d0f6a566cd06d4fd191a Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 27 May 2026 16:26:11 -0400 Subject: [PATCH 4/4] refactor(java): make default executor provider internal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../com/github/copilot/CopilotClient.java | 4 +-- ...der.java => InternalExecutorProvider.java} | 4 +-- ...der.java => InternalExecutorProvider.java} | 4 +-- ...java => InternalExecutorProviderTest.java} | 26 ++++++++++--------- 4 files changed, 20 insertions(+), 18 deletions(-) rename java/src/main/java/com/github/copilot/{DefaultExecutorProvider.java => InternalExecutorProvider.java} (86%) rename java/src/main/java25/com/github/copilot/{DefaultExecutorProvider.java => InternalExecutorProvider.java} (88%) rename java/src/test/java/com/github/copilot/{DefaultExecutorProviderTest.java => InternalExecutorProviderTest.java} (88%) diff --git a/java/src/main/java/com/github/copilot/CopilotClient.java b/java/src/main/java/com/github/copilot/CopilotClient.java index fa9d00476..b4bd5f2bd 100644 --- a/java/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/src/main/java/com/github/copilot/CopilotClient.java @@ -157,8 +157,8 @@ public CopilotClient(CopilotClientOptions options) { } Executor providedExecutor = this.options.getExecutor(); - this.executor = providedExecutor != null ? providedExecutor : DefaultExecutorProvider.create(); - this.ownedExecutor = providedExecutor == null && DefaultExecutorProvider.isOwned(this.executor) + this.executor = providedExecutor != null ? providedExecutor : InternalExecutorProvider.create(); + this.ownedExecutor = providedExecutor == null && InternalExecutorProvider.isOwned(this.executor) && this.executor instanceof ExecutorService executorService ? executorService : null; this.serverManager = new CliServerManager(this.options); diff --git a/java/src/main/java/com/github/copilot/DefaultExecutorProvider.java b/java/src/main/java/com/github/copilot/InternalExecutorProvider.java similarity index 86% rename from java/src/main/java/com/github/copilot/DefaultExecutorProvider.java rename to java/src/main/java/com/github/copilot/InternalExecutorProvider.java index 6967cdeb9..8657027e8 100644 --- a/java/src/main/java/com/github/copilot/DefaultExecutorProvider.java +++ b/java/src/main/java/com/github/copilot/InternalExecutorProvider.java @@ -7,9 +7,9 @@ import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinPool; -final class DefaultExecutorProvider { +final class InternalExecutorProvider { - private DefaultExecutorProvider() { + private InternalExecutorProvider() { } static Executor create() { diff --git a/java/src/main/java25/com/github/copilot/DefaultExecutorProvider.java b/java/src/main/java25/com/github/copilot/InternalExecutorProvider.java similarity index 88% rename from java/src/main/java25/com/github/copilot/DefaultExecutorProvider.java rename to java/src/main/java25/com/github/copilot/InternalExecutorProvider.java index a0a4740bb..257d0f61e 100644 --- a/java/src/main/java25/com/github/copilot/DefaultExecutorProvider.java +++ b/java/src/main/java25/com/github/copilot/InternalExecutorProvider.java @@ -8,9 +8,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -final class DefaultExecutorProvider { +final class InternalExecutorProvider { - private DefaultExecutorProvider() { + private InternalExecutorProvider() { } static Executor create() { diff --git a/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java b/java/src/test/java/com/github/copilot/InternalExecutorProviderTest.java similarity index 88% rename from java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java rename to java/src/test/java/com/github/copilot/InternalExecutorProviderTest.java index 65365809b..7ec4a420d 100644 --- a/java/src/test/java/com/github/copilot/DefaultExecutorProviderTest.java +++ b/java/src/test/java/com/github/copilot/InternalExecutorProviderTest.java @@ -10,8 +10,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; -import java.lang.reflect.Method; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; @@ -31,14 +32,15 @@ import com.github.copilot.rpc.CopilotClientOptions; -class DefaultExecutorProviderTest { +class InternalExecutorProviderTest { @Test void baseProviderUsesCommonPoolWithoutOwnership() { - Executor executor = DefaultExecutorProvider.create(); + Executor executor = InternalExecutorProvider.create(); assertSame(ForkJoinPool.commonPool(), executor); - assertFalse(DefaultExecutorProvider.isOwned(executor)); + assertFalse(InternalExecutorProvider.isOwned(executor)); + assertFalse(Modifier.isPublic(InternalExecutorProvider.class.getModifiers())); } @Test @@ -62,17 +64,17 @@ void multiReleaseJarUsesOwnedVirtualThreadExecutorOnJdk25() throws Exception { } Path classes = Path.of("target", "classes"); - Path baseClass = classes.resolve("com/github/copilot/DefaultExecutorProvider.class"); - Path java25Class = classes.resolve("META-INF/versions/25/com/github/copilot/DefaultExecutorProvider.class"); - assertTrue(Files.exists(baseClass), "Base DefaultExecutorProvider class must be compiled"); + Path baseClass = classes.resolve("com/github/copilot/InternalExecutorProvider.class"); + Path java25Class = classes.resolve("META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class"); + assertTrue(Files.exists(baseClass), "Base InternalExecutorProvider class must be compiled"); assertTrue(Files.exists(java25Class), "JDK 25 build must compile the multi-release executor provider"); - Path jar = Files.createTempFile("copilot-sdk-default-executor", ".jar"); + Path jar = Files.createTempFile("copilot-sdk-internal-executor", ".jar"); try { createProviderJar(jar, baseClass, java25Class); try (var loader = new URLClassLoader(new URL[]{jar.toUri().toURL()}, null)) { - Class provider = Class.forName("com.github.copilot.DefaultExecutorProvider", true, loader); + Class provider = Class.forName("com.github.copilot.InternalExecutorProvider", true, loader); Method create = provider.getDeclaredMethod("create"); Method isOwned = provider.getDeclaredMethod("isOwned", Executor.class); create.setAccessible(true); @@ -103,7 +105,7 @@ void clientCloseShutsDownOwnedDefaultExecutorOnJdk25() throws Exception { } Path classes = Path.of("target", "classes"); - Path jar = Files.createTempFile("copilot-sdk-client-default-executor", ".jar"); + Path jar = Files.createTempFile("copilot-sdk-client-internal-executor", ".jar"); try { createClassesJar(jar, classes); @@ -144,8 +146,8 @@ private static void createProviderJar(Path jar, Path baseClass, Path java25Class attributes.putValue("Multi-Release", "true"); try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar), manifest)) { - addClass(output, "com/github/copilot/DefaultExecutorProvider.class", baseClass); - addClass(output, "META-INF/versions/25/com/github/copilot/DefaultExecutorProvider.class", java25Class); + addClass(output, "com/github/copilot/InternalExecutorProvider.class", baseClass); + addClass(output, "META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class", java25Class); } }