fix: Clamp availability ping delay to the maximum supported timeout (#32937)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
venglin
2026-08-26 12:00:09 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent cf11b1a188
commit dccf0a98c6
2 changed files with 23 additions and 1 deletions
+10 -1
View File
@@ -9,6 +9,12 @@ import * as settings from "../util/settings";
import utils from "../util/utils";
import Extension from "./extension";
/**
* Upper bound for a `setTimeout` delay. Node.js stores the delay as a 32-bit signed integer; anything above this
* is coerced to `1`, which would turn an ever-growing backoff into a tight loop instead of an ever-longer wait.
*/
const MAX_TIMEOUT = 2147483647;
const RETRIEVE_ON_RECONNECT: readonly {keys: string[]; condition?: (state: KeyValue) => boolean}[] = [
{keys: ["state"]},
{keys: ["brightness"], condition: (state: KeyValue): boolean => state.state === "ON"},
@@ -108,7 +114,10 @@ export default class Availability extends Extension {
// If device did not check in, ping it, if that fails it will be marked as offline
this.timers.set(
device.ieeeAddr,
setTimeout(this.addToPingQueue.bind(this, device), (this.getTimeout(device) + utils.seconds(1) + jitter) * backoff),
setTimeout(
this.addToPingQueue.bind(this, device),
Math.min((this.getTimeout(device) + utils.seconds(1) + jitter) * backoff, MAX_TIMEOUT),
),
);
}
} else {
+13
View File
@@ -633,6 +633,19 @@ describe("Extension: Availability", () => {
expect(devices.QBKG03LM.ping).toHaveBeenCalledTimes(4);
});
it("clamps the ping delay to the maximum supported timeout", async () => {
// `setTimeout` takes a 32-bit signed integer and coerces anything above it to `1`. A delay can exceed
// that either directly, through a long `timeout`, or gradually, once `backoff` has multiplied a normal
// one over successive failures. Unclamped, that turns an ever-longer wait into a tight ping loop.
settings.set(["devices", devices.bulb_color.ieeeAddr, "availability"], {timeout: 40000, max_jitter: 0}); // ~27.8 days
await resetExtension();
// unclamped, the delay collapses to 1ms, so pings would already be looping by now
await setTimeAndAdvanceTimers(utils.seconds(1));
expect(devices.bulb_color.ping).not.toHaveBeenCalled();
});
it("allows to disable backoff", async () => {
settings.set(["availability", "active", "max_jitter"], 0); // easier testing
settings.set(["availability", "active", "backoff"], false);