forked from github/copilot-sdk-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSchedulerShutdownRaceTest.java
More file actions
61 lines (49 loc) · 2.74 KB
/
SchedulerShutdownRaceTest.java
File metadata and controls
61 lines (49 loc) · 2.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
package com.github.copilot.sdk;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import com.github.copilot.sdk.json.MessageOptions;
/**
* Reproduces the race between {@code sendAndWait()} and {@code close()}.
* <p>
* If {@code close()} shuts down the timeout scheduler after
* {@code ensureNotTerminated()} passes but before
* {@code timeoutScheduler.schedule()} executes, the schedule call throws
* {@link RejectedExecutionException}. Without a fix the exception propagates
* uncaught, leaking the event subscription and leaving the returned future
* incomplete.
*/
public class SchedulerShutdownRaceTest {
@SuppressWarnings("unchecked")
@Test
void sendAndWaitShouldReturnFailedFutureWhenSchedulerIsShutDown() throws Exception {
// Build a session via reflection (package-private constructor)
var ctor = CopilotSession.class.getDeclaredConstructor(String.class, JsonRpcClient.class, String.class);
ctor.setAccessible(true);
// Mock JsonRpcClient so send() returns a pending future instead of NPE
var mockRpc = mock(JsonRpcClient.class);
when(mockRpc.invoke(any(), any(), any())).thenReturn(new CompletableFuture<>());
var session = ctor.newInstance("race-test", mockRpc, null);
// Shut down the scheduler without setting isTerminated,
// simulating the race window between ensureNotTerminated() and schedule()
var schedulerField = CopilotSession.class.getDeclaredField("timeoutScheduler");
schedulerField.setAccessible(true);
var scheduler = (ScheduledExecutorService) schedulerField.get(session);
scheduler.shutdownNow();
// With the fix: sendAndWait returns a future that completes exceptionally.
// Without the fix: sendAndWait throws RejectedExecutionException directly.
CompletableFuture<?> result = session.sendAndWait(new MessageOptions().setPrompt("test"), 5000);
assertNotNull(result, "sendAndWait should return a future, not throw");
var ex = assertThrows(ExecutionException.class, () -> result.get(1, TimeUnit.SECONDS));
assertInstanceOf(RejectedExecutionException.class, ex.getCause());
}
}