mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 07:50:22 +00:00
fix(discord): guard gateway cleanup races
This commit is contained in:
committed by
Peter Steinberger
parent
f406b20e50
commit
a79c9d50f7
@@ -85,4 +85,25 @@ describe("createDiscordGatewaySupervisor", () => {
|
||||
expect(() => supervisor.dispose()).not.toThrow();
|
||||
expect(() => supervisor.dispose()).not.toThrow();
|
||||
});
|
||||
|
||||
it("keeps suppressing late gateway errors after dispose", () => {
|
||||
const emitter = new EventEmitter();
|
||||
const runtime = { error: vi.fn() };
|
||||
const supervisor = createDiscordGatewaySupervisor({
|
||||
client: {
|
||||
getPlugin: vi.fn(() => ({ emitter })),
|
||||
} as never,
|
||||
isDisallowedIntentsError: () => false,
|
||||
runtime: runtime as never,
|
||||
});
|
||||
|
||||
supervisor.dispose();
|
||||
|
||||
expect(() =>
|
||||
emitter.emit("error", new Error("Max reconnect attempts (0) reached after code 1005")),
|
||||
).not.toThrow();
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("suppressed late gateway reconnect-exhausted error after dispose"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,14 +94,20 @@ export function createDiscordGatewaySupervisor(params: {
|
||||
),
|
||||
);
|
||||
};
|
||||
const logLateDisposedEvent = (event: DiscordGatewayEvent) => {
|
||||
params.runtime.error?.(
|
||||
danger(`discord: suppressed late gateway ${event.type} error after dispose: ${event.message}`),
|
||||
);
|
||||
};
|
||||
const onGatewayError = (err: unknown) => {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
const event = classifyDiscordGatewayEvent({
|
||||
err,
|
||||
isDisallowedIntentsError: params.isDisallowedIntentsError,
|
||||
});
|
||||
if (phase === "disposed") {
|
||||
logLateDisposedEvent(event);
|
||||
return;
|
||||
}
|
||||
if (phase === "active" && lifecycleHandler) {
|
||||
lifecycleHandler(event);
|
||||
return;
|
||||
@@ -145,7 +151,6 @@ export function createDiscordGatewaySupervisor(params: {
|
||||
lifecycleHandler = undefined;
|
||||
phase = "disposed";
|
||||
pending.length = 0;
|
||||
emitter.removeListener("error", onGatewayError);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,6 +225,26 @@ describe("monitorDiscordProvider", () => {
|
||||
expect(createdBindingManagers[0]?.stop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("disconnects the gateway when startup fails before lifecycle begins after client creation", async () => {
|
||||
const disconnect = vi.fn();
|
||||
clientGetPluginMock.mockImplementation((name: string) =>
|
||||
name === "gateway" ? { emitter: new EventEmitter(), disconnect, isConnected: false } : undefined,
|
||||
);
|
||||
createDiscordMessageHandlerMock.mockImplementationOnce(() => {
|
||||
throw new Error("handler init failed");
|
||||
});
|
||||
|
||||
await expect(
|
||||
monitorDiscordProvider({
|
||||
config: baseConfig(),
|
||||
runtime: baseRuntime(),
|
||||
}),
|
||||
).rejects.toThrow("handler init failed");
|
||||
|
||||
expect(monitorLifecycleMock).not.toHaveBeenCalled();
|
||||
expect(disconnect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not double-stop thread bindings when lifecycle performs cleanup", async () => {
|
||||
await monitorDiscordProvider({
|
||||
config: baseConfig(),
|
||||
|
||||
@@ -797,6 +797,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
let gatewaySupervisor: ReturnType<typeof createDiscordGatewaySupervisor> | undefined;
|
||||
let deactivateMessageHandler: (() => void) | undefined;
|
||||
let autoPresenceController: ReturnType<typeof createDiscordAutoPresenceController> | null = null;
|
||||
let lifecycleGateway: GatewayPlugin | undefined;
|
||||
let earlyGatewayEmitter = gatewaySupervisor?.emitter;
|
||||
let onEarlyGatewayDebug: ((msg: unknown) => void) | undefined;
|
||||
try {
|
||||
@@ -954,7 +955,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
runtime,
|
||||
});
|
||||
|
||||
const lifecycleGateway = client.getPlugin<GatewayPlugin>("gateway");
|
||||
lifecycleGateway = client.getPlugin<GatewayPlugin>("gateway");
|
||||
earlyGatewayEmitter = gatewaySupervisor.emitter;
|
||||
onEarlyGatewayDebug = (msg: unknown) => {
|
||||
if (!(isVerboseForTesting ?? isVerbose)()) {
|
||||
@@ -1181,6 +1182,13 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
if (onEarlyGatewayDebug) {
|
||||
earlyGatewayEmitter?.removeListener("debug", onEarlyGatewayDebug);
|
||||
}
|
||||
if (!lifecycleStarted) {
|
||||
try {
|
||||
lifecycleGateway?.disconnect();
|
||||
} catch (err) {
|
||||
runtime.error?.(danger(`discord: failed to disconnect gateway during startup cleanup: ${String(err)}`));
|
||||
}
|
||||
}
|
||||
gatewaySupervisor?.dispose();
|
||||
if (!lifecycleStarted) {
|
||||
threadBindings.stop();
|
||||
|
||||
Reference in New Issue
Block a user