fix(anpr): guarantee at least one analyze attempt per vehicle detection
CI flake root cause (Gitea runner, anpr-entry.test.ts "records an advisory anpr-skip"): the poll-until-confident loop was a plain `while (Date.now() < deadline)` — zero iterations were possible when the window elapsed between deadline-set and loop-entry (the tests run a 5ms window; a slow runner loses that race). Zero attempts → no frame analyzed → "gave up" → no anpr-skip row → assertion fails. Not a regression: nothing in the recent merges touched this path; the race existed since the poll loop was built. The invariant is real beyond tests: on a sufficiently loaded booth the old loop could silently drop a real car's detection the same way. The loop is now do-while (exit via the existing breaks: confident read, or next tick past the slid deadline/hard cap), so a detection ALWAYS analyzes at least one frame. New regression test forces ANPR_POLL_WINDOW_MS=0 (the CI scenario, made deterministic) and asserts exactly one capture attempt + the recorded skip. Suite 283 green. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -192,7 +192,12 @@ export class AnprBridge {
|
||||
const hardCap = Date.now() + this.#pollMaxMs;
|
||||
let attempts = 0;
|
||||
try {
|
||||
while (Date.now() < Math.min(this.#pollDeadline.get(deviceId) ?? 0, hardCap)) {
|
||||
// DO-while: a detection always analyzes AT LEAST ONE frame, however loaded the
|
||||
// host — a plain while could zero-iterate if the window elapsed between setting
|
||||
// the deadline and reaching the loop (seen as a CI flake with the tests' 5ms
|
||||
// window; on a busy booth it would silently drop a real car's detection). Exit
|
||||
// is via the breaks below (confident read, or next tick would pass the deadline).
|
||||
do {
|
||||
attempts++;
|
||||
const shot = await camera.captureSnapshot({ direction });
|
||||
const r = await this.#vision.analyze(shot.bytes, shot.contentType);
|
||||
@@ -229,7 +234,7 @@ export class AnprBridge {
|
||||
const effDeadline = Math.min(this.#pollDeadline.get(deviceId) ?? 0, hardCap);
|
||||
if (Date.now() + this.#pollMs >= effDeadline) break;
|
||||
await sleep(this.#pollMs);
|
||||
}
|
||||
} while (true);
|
||||
} finally {
|
||||
this.#polling.delete(deviceId);
|
||||
this.#pollDeadline.delete(deviceId);
|
||||
|
||||
Reference in New Issue
Block a user