fix: clear telegram polling cleanup timers

This commit is contained in:
Ayaan Zaidi
2026-03-12 09:30:21 +05:30
committed by Ayaan Zaidi
parent 70abee69e9
commit fbc1bd6f8e
3 changed files with 34 additions and 8 deletions

View File

@@ -123,6 +123,7 @@ Docs: https://docs.openclaw.ai
- Agents/fallback cooldown probing: cap cooldown-bypass probing to one attempt per provider per fallback run so multi-model same-provider cooldown chains can continue to cross-provider fallbacks instead of repeatedly stalling on duplicate cooldown probes. (#41711) Thanks @cgdusek.
- Telegram/direct delivery: bridge direct delivery sends to internal `message:sent` hooks so internal hook listeners observe successful Telegram deliveries. (#40185) Thanks @vincentkoc.
- Dependencies: refresh workspace dependencies except the pinned Carbon package, and harden ACP session-config writes against non-string SDK values so newer ACP clients fail fast instead of tripping type/runtime mismatches.
- Telegram/polling restarts: clear bounded cleanup timeout handles after `runner.stop()` and `bot.stop()` settle so stall recovery no longer leaves stray 15-second timers behind on clean shutdown. (#43188) thanks @kyohwang.
## 2026.3.8

View File

@@ -398,6 +398,20 @@ describe("monitorTelegramProvider (grammY)", () => {
expect(createdBotStops[0]).toHaveBeenCalledTimes(1);
});
it("clears bounded cleanup timers after a clean stop", async () => {
vi.useFakeTimers();
try {
const abort = new AbortController();
mockRunOnceAndAbort(abort);
await monitorTelegramProvider({ token: "tok", abortSignal: abort.signal });
expect(vi.getTimerCount()).toBe(0);
} finally {
vi.useRealTimers();
}
});
it("surfaces non-recoverable errors", async () => {
runSpy.mockImplementationOnce(() =>
makeRunnerStub({

View File

@@ -17,6 +17,23 @@ const POLL_STALL_THRESHOLD_MS = 90_000;
const POLL_WATCHDOG_INTERVAL_MS = 30_000;
const POLL_STOP_GRACE_MS = 15_000;
const waitForGracefulStop = async (stop: () => Promise<void>) => {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([
stop(),
new Promise<void>((resolve) => {
timer = setTimeout(resolve, POLL_STOP_GRACE_MS);
timer.unref?.();
}),
]);
} finally {
if (timer) {
clearTimeout(timer);
}
}
};
type TelegramBot = ReturnType<typeof createTelegramBot>;
type TelegramPollingSessionOpts = {
@@ -271,14 +288,8 @@ export class TelegramPollingSession {
clearTimeout(forceCycleTimer);
}
this.opts.abortSignal?.removeEventListener("abort", stopOnAbort);
await Promise.race([
stopRunner(),
new Promise<void>((resolve) => setTimeout(resolve, POLL_STOP_GRACE_MS)),
]);
await Promise.race([
stopBot(),
new Promise<void>((resolve) => setTimeout(resolve, POLL_STOP_GRACE_MS)),
]);
await waitForGracefulStop(stopRunner);
await waitForGracefulStop(stopBot);
this.#activeRunner = undefined;
if (this.#activeFetchAbort === fetchAbortController) {
this.#activeFetchAbort = undefined;