feat(framework): net 收包超时看门狗(注入 Clock 可测)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
export interface Clock {
|
||||||
|
setTimeout(fn: () => void, ms: number): unknown;
|
||||||
|
clearTimeout(handle: unknown): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const realClock: Clock = {
|
||||||
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
||||||
|
clearTimeout: (h) => clearTimeout(h as any),
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 收包超时看门狗。docs/protocol/01 §4.1:每收一包 feed() 重置;超时调 onTimeout。 */
|
||||||
|
export class HeartbeatWatchdog {
|
||||||
|
private handle: unknown = null;
|
||||||
|
constructor(private ms: number, private onTimeout: () => void, private clock: Clock = realClock) {}
|
||||||
|
|
||||||
|
feed(): void {
|
||||||
|
this.stop();
|
||||||
|
this.handle = this.clock.setTimeout(() => { this.handle = null; this.onTimeout(); }, this.ms);
|
||||||
|
}
|
||||||
|
stop(): void {
|
||||||
|
if (this.handle != null) { this.clock.clearTimeout(this.handle); this.handle = null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { HeartbeatWatchdog, type Clock } from '../../YouleNexus/assets/framework/net/heartbeat.ts';
|
||||||
|
|
||||||
|
function fakeClock() {
|
||||||
|
let seq = 0;
|
||||||
|
const timers = new Map<number, { fn: () => void; at: number }>();
|
||||||
|
let now = 0;
|
||||||
|
const clock: Clock = {
|
||||||
|
setTimeout: (fn, ms) => { const id = ++seq; timers.set(id, { fn, at: now + ms }); return id; },
|
||||||
|
clearTimeout: (id: any) => { timers.delete(id); },
|
||||||
|
};
|
||||||
|
const advance = (ms: number) => {
|
||||||
|
now += ms;
|
||||||
|
for (const [id, t] of [...timers]) if (t.at <= now) { timers.delete(id); t.fn(); }
|
||||||
|
};
|
||||||
|
return { clock, advance };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('超时未 feed 触发回调', () => {
|
||||||
|
const { clock, advance } = fakeClock();
|
||||||
|
let fired = 0;
|
||||||
|
const wd = new HeartbeatWatchdog(30000, () => { fired++; }, clock);
|
||||||
|
wd.feed();
|
||||||
|
advance(30000);
|
||||||
|
assert.equal(fired, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('feed 重置计时,未到阈值不触发', () => {
|
||||||
|
const { clock, advance } = fakeClock();
|
||||||
|
let fired = 0;
|
||||||
|
const wd = new HeartbeatWatchdog(30000, () => { fired++; }, clock);
|
||||||
|
wd.feed();
|
||||||
|
advance(20000);
|
||||||
|
wd.feed(); // 重置
|
||||||
|
advance(20000); // 距上次 feed 仅 20s
|
||||||
|
assert.equal(fired, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stop 后不再触发', () => {
|
||||||
|
const { clock, advance } = fakeClock();
|
||||||
|
let fired = 0;
|
||||||
|
const wd = new HeartbeatWatchdog(30000, () => { fired++; }, clock);
|
||||||
|
wd.feed();
|
||||||
|
wd.stop();
|
||||||
|
advance(30000);
|
||||||
|
assert.equal(fired, 0);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user