Skip to content

Commit 2992530

Browse files
committed
Fix agent shutdown hanging on Ctrl+C
1 parent 5f26a47 commit 2992530

2 files changed

Lines changed: 43 additions & 4 deletions

File tree

src/agent/run.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { HOST_WORKSPACE_NAME } from '../shared/types';
66
import { DEFAULT_AGENT_PORT } from '../shared/constants';
77
import { WorkspaceManager } from '../workspace/manager';
88
import { containerRunning, getContainerName } from '../docker';
9-
import { startEagerImagePull } from '../docker/eager-pull';
9+
import { startEagerImagePull, stopEagerImagePull } from '../docker/eager-pull';
1010
import { TerminalWebSocketServer } from '../terminal/websocket';
1111
import { ChatWebSocketServer } from '../chat/websocket';
1212
import { OpencodeWebSocketServer } from '../chat/opencode-websocket';
@@ -209,12 +209,33 @@ export async function startAgent(options: StartAgentOptions = {}): Promise<void>
209209
startEagerImagePull();
210210
});
211211

212+
let isShuttingDown = false;
213+
212214
const shutdown = () => {
215+
if (isShuttingDown) {
216+
console.log('[agent] Force exit');
217+
process.exit(0);
218+
}
219+
isShuttingDown = true;
220+
213221
console.log('[agent] Shutting down...');
222+
223+
stopEagerImagePull();
224+
214225
chatServer.close();
215226
opencodeServer.close();
216227
terminalServer.close();
228+
229+
server.closeAllConnections();
230+
231+
const forceExitTimeout = setTimeout(() => {
232+
console.log('[agent] Force exit after timeout');
233+
process.exit(0);
234+
}, 3000);
235+
forceExitTimeout.unref();
236+
217237
server.close(() => {
238+
clearTimeout(forceExitTimeout);
218239
console.log('[agent] Server closed');
219240
process.exit(0);
220241
});

src/docker/eager-pull.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const MAX_RETRIES = 10;
77

88
let pullInProgress = false;
99
let pullComplete = false;
10+
let abortController: AbortController | null = null;
1011

1112
async function isDockerAvailable(): Promise<boolean> {
1213
try {
@@ -44,8 +45,15 @@ export async function startEagerImagePull(): Promise<void> {
4445
}
4546

4647
pullInProgress = true;
48+
abortController = new AbortController();
49+
const signal = abortController.signal;
4750

4851
const attemptPull = async (attempt: number): Promise<void> => {
52+
if (signal.aborted) {
53+
pullInProgress = false;
54+
return;
55+
}
56+
4957
if (attempt > MAX_RETRIES) {
5058
console.log('[agent] Max retries reached for image pull - giving up background pull');
5159
pullInProgress = false;
@@ -58,7 +66,8 @@ export async function startEagerImagePull(): Promise<void> {
5866
if (attempt === 1) {
5967
console.log('[agent] Docker not available - will retry in background');
6068
}
61-
setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
69+
const timer = setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
70+
timer.unref();
6271
return;
6372
}
6473

@@ -67,14 +76,23 @@ export async function startEagerImagePull(): Promise<void> {
6776
if (success) {
6877
pullComplete = true;
6978
pullInProgress = false;
70-
} else {
71-
setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
79+
} else if (!signal.aborted) {
80+
const timer = setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
81+
timer.unref();
7282
}
7383
};
7484

7585
attemptPull(1);
7686
}
7787

88+
export function stopEagerImagePull(): void {
89+
if (abortController) {
90+
abortController.abort();
91+
abortController = null;
92+
}
93+
pullInProgress = false;
94+
}
95+
7896
export function isImagePullComplete(): boolean {
7997
return pullComplete;
8098
}

0 commit comments

Comments
 (0)