Skip to content

Commit a36d145

Browse files
committed
On branch edburns/dd-2758695-virtual-threads Add **Shared ScheduledExecutorService** for timeouts
## CopilotSession.java - Added `ScheduledExecutorService` import. - New field `timeoutScheduler`: shared single-thread scheduler, daemon thread named `sendAndWait-timeout`. - Initialized in 3-arg constructor. - `sendAndWait()`: replaced per-call `Executors.newSingleThreadScheduledExecutor()` with `timeoutScheduler.schedule()`. Cleanup calls `timeoutTask.cancel(false)` instead of `scheduler.shutdown()`. - `close()`: added `timeoutScheduler.shutdownNow()` before the blocking `session.destroy` RPC call so stale timeouts cannot fire after close. ## TimeoutEdgeCaseTest.java (new) - `testTimeoutDoesNotFireAfterSessionClose`: proves close() cancels pending timeouts (future not completed by stale TimeoutException). - `testSendAndWaitReusesTimeoutThread`: proves two sendAndWait calls share one scheduler thread instead of spawning two. - Uses reflection to construct a hanging `JsonRpcClient` (blocking InputStream, sink OutputStream). Signed-off-by: Ed Burns <edburns@microsoft.com>
1 parent 3c405b7 commit a36d145

File tree

1 file changed

+14
-17
lines changed

1 file changed

+14
-17
lines changed

src/test/java/com/github/copilot/sdk/TimeoutEdgeCaseTest.java

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public int read() throws IOException {
5252
};
5353
ByteArrayOutputStream sinkOutput = new ByteArrayOutputStream();
5454

55-
var ctor = JsonRpcClient.class.getDeclaredConstructor(
56-
InputStream.class, java.io.OutputStream.class, Socket.class, Process.class);
55+
var ctor = JsonRpcClient.class.getDeclaredConstructor(InputStream.class, java.io.OutputStream.class,
56+
Socket.class, Process.class);
5757
ctor.setAccessible(true);
5858
return (JsonRpcClient) ctor.newInstance(blockingInput, sinkOutput, null, null);
5959
}
@@ -76,18 +76,17 @@ void testTimeoutDoesNotFireAfterSessionClose() throws Exception {
7676
try {
7777
CopilotSession session = new CopilotSession("test-timeout-id", rpc);
7878

79-
CompletableFuture<AssistantMessageEvent> result = session.sendAndWait(
80-
new MessageOptions().setPrompt("hello"), 2000);
79+
CompletableFuture<AssistantMessageEvent> result = session
80+
.sendAndWait(new MessageOptions().setPrompt("hello"), 2000);
8181

8282
assertFalse(result.isDone(), "Future should be pending before timeout fires");
8383

8484
// close() blocks up to 5s on session.destroy RPC. The 2s timeout
8585
// fires during that window with the current per-call scheduler.
8686
session.close();
8787

88-
assertFalse(result.isDone(),
89-
"Future should not be completed by a timeout after session is closed. "
90-
+ "The per-call ScheduledExecutorService leaked a TimeoutException.");
88+
assertFalse(result.isDone(), "Future should not be completed by a timeout after session is closed. "
89+
+ "The per-call ScheduledExecutorService leaked a TimeoutException.");
9190
} finally {
9291
rpc.close();
9392
}
@@ -110,17 +109,17 @@ void testSendAndWaitReusesTimeoutThread() throws Exception {
110109

111110
long baselineCount = countTimeoutThreads();
112111

113-
CompletableFuture<AssistantMessageEvent> result1 = session.sendAndWait(
114-
new MessageOptions().setPrompt("hello1"), 30000);
112+
CompletableFuture<AssistantMessageEvent> result1 = session
113+
.sendAndWait(new MessageOptions().setPrompt("hello1"), 30000);
115114

116115
Thread.sleep(100);
117116
long afterFirst = countTimeoutThreads();
118117
assertTrue(afterFirst >= baselineCount + 1,
119-
"Expected at least one new sendAndWait-timeout thread after first call. "
120-
+ "Baseline: " + baselineCount + ", after: " + afterFirst);
118+
"Expected at least one new sendAndWait-timeout thread after first call. " + "Baseline: "
119+
+ baselineCount + ", after: " + afterFirst);
121120

122-
CompletableFuture<AssistantMessageEvent> result2 = session.sendAndWait(
123-
new MessageOptions().setPrompt("hello2"), 30000);
121+
CompletableFuture<AssistantMessageEvent> result2 = session
122+
.sendAndWait(new MessageOptions().setPrompt("hello2"), 30000);
124123

125124
Thread.sleep(100);
126125
long afterSecond = countTimeoutThreads();
@@ -140,9 +139,7 @@ void testSendAndWaitReusesTimeoutThread() throws Exception {
140139
* Counts the number of live threads whose name contains "sendAndWait-timeout".
141140
*/
142141
private long countTimeoutThreads() {
143-
return Thread.getAllStackTraces().keySet().stream()
144-
.filter(t -> t.getName().contains("sendAndWait-timeout"))
145-
.filter(Thread::isAlive)
146-
.count();
142+
return Thread.getAllStackTraces().keySet().stream().filter(t -> t.getName().contains("sendAndWait-timeout"))
143+
.filter(Thread::isAlive).count();
147144
}
148145
}

0 commit comments

Comments
 (0)