mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 21:40:24 +00:00
fix: handle intentional signal daemon shutdown on abort (#23379) (thanks @frankekn)
This commit is contained in:
@@ -246,6 +246,43 @@ describe("monitorSignalProvider tool results", () => {
|
||||
).rejects.toThrow(/signal daemon exited/i);
|
||||
});
|
||||
|
||||
it("treats daemon exit after user abort as clean shutdown", async () => {
|
||||
const runtime = createMonitorRuntime();
|
||||
setSignalAutoStartConfig();
|
||||
const abortController = new AbortController();
|
||||
let exited = false;
|
||||
let resolveExit!: (value: { code: number | null; signal: NodeJS.Signals | null }) => void;
|
||||
const exitedPromise = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
|
||||
(resolve) => {
|
||||
resolveExit = resolve;
|
||||
},
|
||||
);
|
||||
const stop = vi.fn(() => {
|
||||
if (exited) {
|
||||
return;
|
||||
}
|
||||
exited = true;
|
||||
resolveExit({ code: null, signal: "SIGTERM" });
|
||||
});
|
||||
spawnSignalDaemonMock.mockReturnValueOnce({
|
||||
stop,
|
||||
exited: exitedPromise,
|
||||
isExited: () => exited,
|
||||
});
|
||||
streamMock.mockImplementationOnce(async () => {
|
||||
abortController.abort(new Error("stop"));
|
||||
});
|
||||
|
||||
await expect(
|
||||
runMonitorWithMocks({
|
||||
autoStart: true,
|
||||
baseUrl: SIGNAL_BASE_URL,
|
||||
runtime,
|
||||
abortSignal: abortController.signal,
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("skips tool summaries with responsePrefix", async () => {
|
||||
replyMock.mockResolvedValue({ text: "final reply" });
|
||||
|
||||
|
||||
@@ -330,6 +330,11 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
const daemonAbortController = new AbortController();
|
||||
const mergedAbort = mergeAbortSignals(opts.abortSignal, daemonAbortController.signal);
|
||||
let daemonHandle: ReturnType<typeof spawnSignalDaemon> | null = null;
|
||||
let daemonStopRequested = false;
|
||||
const stopDaemon = () => {
|
||||
daemonStopRequested = true;
|
||||
daemonHandle?.stop();
|
||||
};
|
||||
|
||||
if (autoStart) {
|
||||
const cliPath = opts.cliPath ?? accountInfo.config.cliPath ?? "signal-cli";
|
||||
@@ -347,6 +352,9 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
runtime,
|
||||
});
|
||||
void daemonHandle.exited.then((exit) => {
|
||||
if (daemonStopRequested || opts.abortSignal?.aborted) {
|
||||
return;
|
||||
}
|
||||
daemonExitError = new Error(
|
||||
`signal daemon exited (code=${String(exit.code ?? "null")} signal=${String(exit.signal ?? "null")})`,
|
||||
);
|
||||
@@ -357,7 +365,7 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
}
|
||||
|
||||
const onAbort = () => {
|
||||
daemonHandle?.stop();
|
||||
stopDaemon();
|
||||
};
|
||||
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
||||
|
||||
@@ -426,6 +434,6 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
} finally {
|
||||
mergedAbort.dispose();
|
||||
opts.abortSignal?.removeEventListener("abort", onAbort);
|
||||
daemonHandle?.stop();
|
||||
stopDaemon();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user