mirror of
https://github.com/morluto/rea.git
synced 2026-10-11 21:39:55 +00:00
fix(process): observe already-due trailing callbacks before settling the capture journal (#1700)
A stalled event loop can leave the settle wake-up and a trailing observation callback due at the same resume. The earlier wake-up runs first, declares silence, and the capture becomes immutable before the trailing callback appends its observation. Drain the loop turn after the quiet sleep so timer and poll callbacks that were already due run before silence is judged, and cover the stalled-loop case with a deterministic regression test. Fixes #1697
This commit is contained in:
@@ -3,7 +3,7 @@ import { constants, statSync, type BigIntStats } from "node:fs";
|
||||
import { open, mkdtemp, rm, stat } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { setImmediate, setTimeout as delay } from "node:timers/promises";
|
||||
import type { IPty } from "@lydell/node-pty";
|
||||
|
||||
import type { ProcessCapture } from "../../domain/process/processCaptureParsing.js";
|
||||
@@ -271,6 +271,10 @@ export const settleProcessCaptureJournal = async (
|
||||
let lastChangeAt = Date.now();
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 4));
|
||||
// A stalled event loop can leave timer and poll callbacks that were already
|
||||
// due queued behind this wake-up. Drain them before judging silence so a
|
||||
// trailing observation is not dropped by an early settle.
|
||||
await setImmediate();
|
||||
if (journal.length !== observed) {
|
||||
observed = journal.length;
|
||||
lastChangeAt = Date.now();
|
||||
|
||||
@@ -274,6 +274,33 @@ it("waits for terminal observations delivered after the exit callback", async ()
|
||||
expect(journal[1]).toMatchObject({ collection: "frames", index: 1 });
|
||||
});
|
||||
|
||||
it("does not settle while a trailing observation is already due behind a stalled event loop", async () => {
|
||||
const journal: Array<{
|
||||
capture_order: number;
|
||||
collection: "frames";
|
||||
index: number;
|
||||
}> = [{ capture_order: 0, collection: "frames", index: 0 }];
|
||||
const lateFrame = new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
journal.push({ capture_order: 1, collection: "frames", index: 1 });
|
||||
resolve();
|
||||
}, 12);
|
||||
});
|
||||
|
||||
const wait = settleProcessCaptureJournal(journal, 20, 200);
|
||||
// Stall the event loop past the quiet window so the settle wake-up and the
|
||||
// trailing observation are both already due when timer callbacks resume.
|
||||
const stalledUntil = Date.now() + 40;
|
||||
while (Date.now() < stalledUntil) {
|
||||
// Deliberately occupy the loop; no timer can run until this returns.
|
||||
}
|
||||
await wait;
|
||||
|
||||
expect(journal).toHaveLength(2);
|
||||
expect(journal[1]).toMatchObject({ collection: "frames", index: 1 });
|
||||
await lateFrame;
|
||||
});
|
||||
|
||||
it("keeps empty cleanup exception messages actionable in the report", async () => {
|
||||
const host: ProcessCaptureCleanupHost = {
|
||||
platform: process.platform,
|
||||
|
||||
Reference in New Issue
Block a user