|
13 | 13 | import java.util.concurrent.CompletableFuture; |
14 | 14 | import java.util.concurrent.CompletionException; |
15 | 15 | import java.util.concurrent.ConcurrentHashMap; |
| 16 | +import java.util.concurrent.Executor; |
| 17 | +import java.util.concurrent.RejectedExecutionException; |
16 | 18 | import java.util.concurrent.TimeUnit; |
17 | 19 | import java.util.logging.Level; |
18 | 20 | import java.util.logging.Logger; |
@@ -150,42 +152,51 @@ public CompletableFuture<Void> start() { |
150 | 152 | private CompletableFuture<Connection> startCore() { |
151 | 153 | LOG.fine("Starting Copilot client"); |
152 | 154 |
|
153 | | - return CompletableFuture.supplyAsync(() -> { |
154 | | - try { |
155 | | - JsonRpcClient rpc; |
156 | | - Process process = null; |
157 | | - |
158 | | - if (optionsHost != null && optionsPort != null) { |
159 | | - // External server (TCP) |
160 | | - rpc = serverManager.connectToServer(null, optionsHost, optionsPort); |
161 | | - } else { |
162 | | - // Child process (stdio or TCP) |
163 | | - CliServerManager.ProcessInfo processInfo = serverManager.startCliServer(); |
164 | | - process = processInfo.process(); |
165 | | - rpc = serverManager.connectToServer(process, processInfo.port() != null ? "localhost" : null, |
166 | | - processInfo.port()); |
167 | | - } |
| 155 | + Executor exec = options.getExecutor(); |
| 156 | + try { |
| 157 | + return exec != null |
| 158 | + ? CompletableFuture.supplyAsync(this::startCoreBody, exec) |
| 159 | + : CompletableFuture.supplyAsync(this::startCoreBody); |
| 160 | + } catch (RejectedExecutionException e) { |
| 161 | + return CompletableFuture.failedFuture(e); |
| 162 | + } |
| 163 | + } |
168 | 164 |
|
169 | | - Connection connection = new Connection(rpc, process); |
| 165 | + private Connection startCoreBody() { |
| 166 | + try { |
| 167 | + JsonRpcClient rpc; |
| 168 | + Process process = null; |
| 169 | + |
| 170 | + if (optionsHost != null && optionsPort != null) { |
| 171 | + // External server (TCP) |
| 172 | + rpc = serverManager.connectToServer(null, optionsHost, optionsPort); |
| 173 | + } else { |
| 174 | + // Child process (stdio or TCP) |
| 175 | + CliServerManager.ProcessInfo processInfo = serverManager.startCliServer(); |
| 176 | + process = processInfo.process(); |
| 177 | + rpc = serverManager.connectToServer(process, processInfo.port() != null ? "localhost" : null, |
| 178 | + processInfo.port()); |
| 179 | + } |
170 | 180 |
|
171 | | - // Register handlers for server-to-client calls |
172 | | - RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch); |
173 | | - dispatcher.registerHandlers(rpc); |
| 181 | + Connection connection = new Connection(rpc, process); |
174 | 182 |
|
175 | | - // Verify protocol version |
176 | | - verifyProtocolVersion(connection); |
| 183 | + // Register handlers for server-to-client calls |
| 184 | + RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, |
| 185 | + options.getExecutor()); |
| 186 | + dispatcher.registerHandlers(rpc); |
177 | 187 |
|
178 | | - LOG.info("Copilot client connected"); |
179 | | - return connection; |
180 | | - } catch (Exception e) { |
181 | | - String stderr = serverManager.getStderrOutput(); |
182 | | - if (!stderr.isEmpty()) { |
183 | | - throw new CompletionException( |
184 | | - new IOException("CLI process exited unexpectedly. stderr: " + stderr, e)); |
185 | | - } |
186 | | - throw new CompletionException(e); |
| 188 | + // Verify protocol version |
| 189 | + verifyProtocolVersion(connection); |
| 190 | + |
| 191 | + LOG.info("Copilot client connected"); |
| 192 | + return connection; |
| 193 | + } catch (Exception e) { |
| 194 | + String stderr = serverManager.getStderrOutput(); |
| 195 | + if (!stderr.isEmpty()) { |
| 196 | + throw new CompletionException(new IOException("CLI process exited unexpectedly. stderr: " + stderr, e)); |
187 | 197 | } |
188 | | - }); |
| 198 | + throw new CompletionException(e); |
| 199 | + } |
189 | 200 | } |
190 | 201 |
|
191 | 202 | private static final int MIN_PROTOCOL_VERSION = 2; |
@@ -228,15 +239,27 @@ private void verifyProtocolVersion(Connection connection) throws Exception { |
228 | 239 | */ |
229 | 240 | public CompletableFuture<Void> stop() { |
230 | 241 | var closeFutures = new ArrayList<CompletableFuture<Void>>(); |
| 242 | + Executor exec = options.getExecutor(); |
231 | 243 |
|
232 | 244 | for (CopilotSession session : new ArrayList<>(sessions.values())) { |
233 | | - closeFutures.add(CompletableFuture.runAsync(() -> { |
| 245 | + Runnable closeTask = () -> { |
234 | 246 | try { |
235 | 247 | session.close(); |
236 | 248 | } catch (Exception e) { |
237 | 249 | LOG.log(Level.WARNING, "Error closing session " + session.getSessionId(), e); |
238 | 250 | } |
239 | | - })); |
| 251 | + }; |
| 252 | + CompletableFuture<Void> future; |
| 253 | + try { |
| 254 | + future = exec != null |
| 255 | + ? CompletableFuture.runAsync(closeTask, exec) |
| 256 | + : CompletableFuture.runAsync(closeTask); |
| 257 | + } catch (RejectedExecutionException e) { |
| 258 | + LOG.log(Level.WARNING, "Executor rejected session close task; closing inline", e); |
| 259 | + closeTask.run(); |
| 260 | + future = CompletableFuture.completedFuture(null); |
| 261 | + } |
| 262 | + closeFutures.add(future); |
240 | 263 | } |
241 | 264 | sessions.clear(); |
242 | 265 |
|
@@ -329,6 +352,9 @@ public CompletableFuture<CopilotSession> createSession(SessionConfig config) { |
329 | 352 | : java.util.UUID.randomUUID().toString(); |
330 | 353 |
|
331 | 354 | var session = new CopilotSession(sessionId, connection.rpc); |
| 355 | + if (options.getExecutor() != null) { |
| 356 | + session.setExecutor(options.getExecutor()); |
| 357 | + } |
332 | 358 | SessionRequestBuilder.configureSession(session, config); |
333 | 359 | sessions.put(sessionId, session); |
334 | 360 |
|
@@ -399,6 +425,9 @@ public CompletableFuture<CopilotSession> resumeSession(String sessionId, ResumeS |
399 | 425 | return ensureConnected().thenCompose(connection -> { |
400 | 426 | // Register the session before the RPC call to avoid missing early events. |
401 | 427 | var session = new CopilotSession(sessionId, connection.rpc); |
| 428 | + if (options.getExecutor() != null) { |
| 429 | + session.setExecutor(options.getExecutor()); |
| 430 | + } |
402 | 431 | SessionRequestBuilder.configureSession(session, config); |
403 | 432 | sessions.put(sessionId, session); |
404 | 433 |
|
|
0 commit comments