mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-29 21:48:03 +02:00
fix(discord): stop cleanly during reconnect drain
This commit is contained in:
@@ -479,6 +479,41 @@ describe("runDiscordGatewayLifecycle", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("treats drain timeout as a graceful stop after lifecycle abort", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const { runDiscordGatewayLifecycle } = await import("./provider.lifecycle.js");
|
||||
const socket = new EventEmitter();
|
||||
const { emitter, gateway } = createGatewayHarness({ ws: socket });
|
||||
getDiscordGatewayEmitterMock.mockReturnValueOnce(emitter);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const { lifecycleParams, start, stop, threadStop, runtimeError, gatewaySupervisor } =
|
||||
createLifecycleHarness({ gateway });
|
||||
lifecycleParams.abortSignal = abortController.signal;
|
||||
|
||||
const lifecyclePromise = runDiscordGatewayLifecycle(lifecycleParams);
|
||||
await vi.advanceTimersByTimeAsync(15_100);
|
||||
abortController.abort();
|
||||
await vi.advanceTimersByTimeAsync(5_500);
|
||||
await expect(lifecyclePromise).resolves.toBeUndefined();
|
||||
|
||||
expect(gateway.connect).not.toHaveBeenCalled();
|
||||
expect(runtimeError).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining("gateway socket did not close within 5000ms before reconnect"),
|
||||
);
|
||||
expectLifecycleCleanup({
|
||||
start,
|
||||
stop,
|
||||
threadStop,
|
||||
waitCalls: 0,
|
||||
gatewaySupervisor,
|
||||
});
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("fails fast when startup never reaches READY after a forced reconnect", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
||||
@@ -219,7 +219,8 @@ export async function runDiscordGatewayLifecycle(params: {
|
||||
let drainTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
let terminateCloseTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
const ignoreSocketError = () => {};
|
||||
const cleanup = () => {
|
||||
const shouldStopWaiting = () => lifecycleStopping || params.abortSignal?.aborted;
|
||||
const clearPendingTimers = () => {
|
||||
if (drainTimeout) {
|
||||
clearTimeout(drainTimeout);
|
||||
drainTimeout = undefined;
|
||||
@@ -228,6 +229,9 @@ export async function runDiscordGatewayLifecycle(params: {
|
||||
clearTimeout(terminateCloseTimeout);
|
||||
terminateCloseTimeout = undefined;
|
||||
}
|
||||
};
|
||||
const cleanup = () => {
|
||||
clearPendingTimers();
|
||||
socket.removeListener("close", onClose);
|
||||
socket.removeListener("error", ignoreSocketError);
|
||||
};
|
||||
@@ -239,19 +243,28 @@ export async function runDiscordGatewayLifecycle(params: {
|
||||
settled = true;
|
||||
resolve();
|
||||
};
|
||||
const rejectClose = (error: Error) => {
|
||||
const resolveStoppedWait = () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
if (drainTimeout) {
|
||||
clearTimeout(drainTimeout);
|
||||
drainTimeout = undefined;
|
||||
clearPendingTimers();
|
||||
|
||||
// Keep suppressing late ws errors until the socket actually closes.
|
||||
// The original Carbon listeners were removed above, and `terminate()`
|
||||
// can still asynchronously emit "error" before "close".
|
||||
resolve();
|
||||
};
|
||||
const rejectClose = (error: Error) => {
|
||||
if (shouldStopWaiting()) {
|
||||
resolveStoppedWait();
|
||||
return;
|
||||
}
|
||||
if (terminateCloseTimeout) {
|
||||
clearTimeout(terminateCloseTimeout);
|
||||
terminateCloseTimeout = undefined;
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearPendingTimers();
|
||||
|
||||
// Keep suppressing late ws errors until the socket actually closes.
|
||||
// The original Carbon listeners were removed above, and `terminate()`
|
||||
@@ -263,6 +276,10 @@ export async function runDiscordGatewayLifecycle(params: {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
if (shouldStopWaiting()) {
|
||||
resolveStoppedWait();
|
||||
return;
|
||||
}
|
||||
params.runtime.error?.(
|
||||
danger(
|
||||
`discord: gateway socket did not close within ${DISCORD_GATEWAY_DISCONNECT_DRAIN_TIMEOUT_MS}ms before reconnect; attempting forced terminate before giving up`,
|
||||
@@ -298,6 +315,10 @@ export async function runDiscordGatewayLifecycle(params: {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
if (shouldStopWaiting()) {
|
||||
resolveStoppedWait();
|
||||
return;
|
||||
}
|
||||
params.runtime.error?.(
|
||||
danger(
|
||||
`discord: gateway socket did not close ${DISCORD_GATEWAY_FORCE_TERMINATE_CLOSE_TIMEOUT_MS}ms after forced terminate; force-stopping instead of opening a parallel socket`,
|
||||
|
||||
Reference in New Issue
Block a user