fix(telegram): keep polling watchdog active for wedged runner · openclaw/openclaw@ceace83
vincentkoc
·
2026-04-25
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
|
18 | 18 | |
19 | 19 | - Heartbeat: clamp oversized scheduler delays through the shared safe timer helper, preventing `every` values over Node's timeout cap from becoming a 1 ms crash loop. Fixes #71414. (#71478) Thanks @hclsys. |
20 | 20 | - Telegram: remove the startup persisted-offset `getUpdates` preflight so polling restarts do not self-conflict before the runner starts. Fixes #69304. (#69779) Thanks @chinar-amrutkar. |
| 21 | +- Telegram: keep the polling stall watchdog active even when grammY reports the runner as not running while its task is still pending, so a rebuilt transport cannot leave `getUpdates` silent until a manual gateway restart. Fixes #69064. Thanks @LDLoeb. |
21 | 22 | - Browser/Playwright: ignore benign already-handled route races during guarded navigation so browser-page tasks no longer fail when Playwright tears down a route mid-flight. (#68708) Thanks @Steady-ai. |
22 | 23 | - Browser/downloads: seed managed Chrome profiles with OpenClaw download prefs and capture unmanaged click-triggered downloads under the guarded downloads directory, while explicit download waiters still own their target file. (#64558) Thanks @Pearcekieser. |
23 | 24 | - Browser/Chrome: stop passing redundant `--disable-setuid-sandbox` when `browser.noSandbox` is enabled; `--no-sandbox` remains the effective sandbox opt-out. (#67939) Thanks @sebykrueger. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -31,7 +31,6 @@ describe("TelegramPollingLivenessTracker", () => {
|
31 | 31 | expect( |
32 | 32 | tracker.detectStall({ |
33 | 33 | thresholdMs: POLL_STALL_THRESHOLD_MS, |
34 | | -runnerIsRunning: true, |
35 | 34 | }), |
36 | 35 | ).toBeNull(); |
37 | 36 | |
@@ -45,7 +44,6 @@ describe("TelegramPollingLivenessTracker", () => {
|
45 | 44 | now = 120_001; |
46 | 45 | const stall = tracker.detectStall({ |
47 | 46 | thresholdMs: POLL_STALL_THRESHOLD_MS, |
48 | | -runnerIsRunning: true, |
49 | 47 | }); |
50 | 48 | expect(stall?.message).toContain("Polling stall detected (no completed getUpdates"); |
51 | 49 | expect(stall?.message).toContain("inFlight=0 outcome=not-started"); |
@@ -54,7 +52,6 @@ describe("TelegramPollingLivenessTracker", () => {
|
54 | 52 | expect( |
55 | 53 | tracker.detectStall({ |
56 | 54 | thresholdMs: POLL_STALL_THRESHOLD_MS, |
57 | | -runnerIsRunning: true, |
58 | 55 | }), |
59 | 56 | ).toBeNull(); |
60 | 57 | }); |
@@ -69,7 +66,6 @@ describe("TelegramPollingLivenessTracker", () => {
|
69 | 66 | now = 120_001; |
70 | 67 | const stall = tracker.detectStall({ |
71 | 68 | thresholdMs: POLL_STALL_THRESHOLD_MS, |
72 | | -runnerIsRunning: true, |
73 | 69 | }); |
74 | 70 | |
75 | 71 | expect(stall?.message).toContain("active getUpdates stuck"); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -89,14 +89,7 @@ export class TelegramPollingLivenessTracker {
|
89 | 89 | this.#inFlightGetUpdates = Math.max(0, this.#inFlightGetUpdates - 1); |
90 | 90 | } |
91 | 91 | |
92 | | -detectStall(params: { |
93 | | -thresholdMs: number; |
94 | | -runnerIsRunning: boolean; |
95 | | -now?: number; |
96 | | -}): TelegramPollingStall | null { |
97 | | -if (!params.runnerIsRunning) { |
98 | | -return null; |
99 | | -} |
| 92 | +detectStall(params: { thresholdMs: number; now?: number }): TelegramPollingStall | null { |
100 | 93 | const now = params.now ?? this.#now(); |
101 | 94 | const activeElapsed = |
102 | 95 | this.#inFlightGetUpdates > 0 && this.#lastGetUpdatesStartedAt != null |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -387,6 +387,60 @@ describe("TelegramPollingSession", () => {
|
387 | 387 | } |
388 | 388 | }); |
389 | 389 | |
| 390 | +it("forces a restart when the runner task is pending but reports not running", async () => { |
| 391 | +const abort = new AbortController(); |
| 392 | +const firstRunnerStop = vi.fn(async () => undefined); |
| 393 | +const secondRunnerStop = vi.fn(async () => undefined); |
| 394 | +createTelegramBotMock.mockReturnValue(makeBot()); |
| 395 | + |
| 396 | +let firstTaskResolve: (() => void) | undefined; |
| 397 | +const firstTask = new Promise<void>((resolve) => { |
| 398 | +firstTaskResolve = resolve; |
| 399 | +}); |
| 400 | +let cycle = 0; |
| 401 | +runMock.mockImplementation(() => { |
| 402 | +cycle += 1; |
| 403 | +if (cycle === 1) { |
| 404 | +return { |
| 405 | +task: () => firstTask, |
| 406 | +stop: async () => { |
| 407 | +await firstRunnerStop(); |
| 408 | +firstTaskResolve?.(); |
| 409 | +}, |
| 410 | +isRunning: () => false, |
| 411 | +}; |
| 412 | +} |
| 413 | +return { |
| 414 | +task: async () => { |
| 415 | +abort.abort(); |
| 416 | +}, |
| 417 | +stop: secondRunnerStop, |
| 418 | +isRunning: () => false, |
| 419 | +}; |
| 420 | +}); |
| 421 | + |
| 422 | +const watchdogHarness = installPollingStallWatchdogHarness(); |
| 423 | + |
| 424 | +const log = vi.fn(); |
| 425 | +const session = createPollingSession({ |
| 426 | +abortSignal: abort.signal, |
| 427 | + log, |
| 428 | +}); |
| 429 | + |
| 430 | +try { |
| 431 | +const runPromise = session.runUntilAbort(); |
| 432 | +const watchdog = await watchdogHarness.waitForWatchdog(); |
| 433 | +watchdog?.(); |
| 434 | +await runPromise; |
| 435 | + |
| 436 | +expect(runMock).toHaveBeenCalledTimes(2); |
| 437 | +expect(firstRunnerStop).toHaveBeenCalledTimes(1); |
| 438 | +expect(log).toHaveBeenCalledWith(expect.stringContaining("Polling stall detected")); |
| 439 | +} finally { |
| 440 | +watchdogHarness.restore(); |
| 441 | +} |
| 442 | +}); |
| 443 | + |
390 | 444 | it("honors a custom polling stall threshold", async () => { |
391 | 445 | const abort = new AbortController(); |
392 | 446 | const botStop = vi.fn(async () => undefined); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -295,7 +295,6 @@ export class TelegramPollingSession {
|
295 | 295 | |
296 | 296 | const stall = liveness.detectStall({ |
297 | 297 | thresholdMs: this.#stallThresholdMs, |
298 | | -runnerIsRunning: runner.isRunning(), |
299 | 298 | }); |
300 | 299 | if (stall) { |
301 | 300 | this.#transportState.markDirty(); |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。