@rallycry/conveyor-agent 10.2.1 → 10.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,60 @@
1
+ // src/connection/loop-lag.ts
2
+ var LOOP_TICK_INTERVAL_MS = 250;
3
+ var LOOP_STALL_LOG_MS = 1e3;
4
+ var LOOP_STATUS_VALUES = ["active", "idle", "building"];
5
+ function lagFromBuffer(view) {
6
+ const last = view[0];
7
+ if (!last) return 0;
8
+ return Math.max(0, Date.now() - last - LOOP_TICK_INTERVAL_MS);
9
+ }
10
+ function statusFromBuffer(view) {
11
+ return LOOP_STATUS_VALUES[view[1]] ?? "active";
12
+ }
13
+ var LoopLagMonitor = class {
14
+ sharedBuffer = new SharedArrayBuffer(2 * Float64Array.BYTES_PER_ELEMENT);
15
+ view = new Float64Array(this.sharedBuffer);
16
+ timer = null;
17
+ maxLagMs = 0;
18
+ start() {
19
+ if (this.timer) return;
20
+ this.view[0] = Date.now();
21
+ this.timer = setInterval(() => {
22
+ const now = Date.now();
23
+ const lag = Math.max(0, now - this.view[0] - LOOP_TICK_INTERVAL_MS);
24
+ if (lag >= LOOP_STALL_LOG_MS) {
25
+ this.maxLagMs = Math.max(this.maxLagMs, lag);
26
+ process.stderr.write(
27
+ `[conveyor-agent] event-loop stall: ${Math.round(lag)}ms (${(/* @__PURE__ */ new Date()).toISOString()})
28
+ `
29
+ );
30
+ }
31
+ this.view[0] = now;
32
+ }, LOOP_TICK_INTERVAL_MS);
33
+ this.timer.unref();
34
+ }
35
+ stop() {
36
+ if (this.timer) clearInterval(this.timer);
37
+ this.timer = null;
38
+ }
39
+ /** Mirror the runner's current status for the worker to report. */
40
+ setStatus(status) {
41
+ const idx = LOOP_STATUS_VALUES.indexOf(status);
42
+ this.view[1] = idx === -1 ? 0 : idx;
43
+ }
44
+ /** Worst stall observed since the previous call (then reset). Includes the
45
+ * live lag so a beat sent mid-stall from this thread — possible when the
46
+ * heartbeat timer fires before the refresher in the same tick batch — is
47
+ * still truthful. */
48
+ takeMaxLagMs() {
49
+ const max = Math.max(this.maxLagMs, lagFromBuffer(this.view));
50
+ this.maxLagMs = 0;
51
+ return max;
52
+ }
53
+ };
54
+
55
+ export {
56
+ lagFromBuffer,
57
+ statusFromBuffer,
58
+ LoopLagMonitor
59
+ };
60
+ //# sourceMappingURL=chunk-7TQO4ZF4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connection/loop-lag.ts"],"sourcesContent":["/**\n * Main event-loop lag measurement, shared with the heartbeat worker.\n *\n * The monitor owns a SharedArrayBuffer the main thread refreshes on a fast\n * timer. Two consumers read it:\n * - the MAIN-loop heartbeat reports `takeMaxLagMs()` — the worst stall\n * observed since the last beat (post-hoc: by the time the heartbeat timer\n * fires after a stall, the refresher has already run, so instantaneous\n * lag would read ~0);\n * - the heartbeat WORKER reads the buffer live from its own thread, where a\n * stalled main loop shows up as a stale tick — that is the signal that\n * keeps the session lease alive while the main loop is starved.\n *\n * Layout (Float64Array): [0] = last main-loop tick (epoch ms),\n * [1] = status index into LOOP_STATUS_VALUES (what the worker should report).\n */\n\nexport const LOOP_TICK_INTERVAL_MS = 250;\n/** Stalls at or above this are logged (and remembered for the next beat). */\nexport const LOOP_STALL_LOG_MS = 1_000;\n\nexport const LOOP_STATUS_VALUES = [\"active\", \"idle\", \"building\"] as const;\nexport type LoopStatus = (typeof LOOP_STATUS_VALUES)[number];\n\n/** Live lag as seen from ANY thread holding the buffer: how stale the main\n * loop's tick is beyond its refresh interval. ~0 while the loop is healthy. */\nexport function lagFromBuffer(view: Float64Array): number {\n const last = view[0];\n if (!last) return 0;\n return Math.max(0, Date.now() - last - LOOP_TICK_INTERVAL_MS);\n}\n\nexport function statusFromBuffer(view: Float64Array): LoopStatus {\n return LOOP_STATUS_VALUES[view[1]] ?? \"active\";\n}\n\nexport class LoopLagMonitor {\n readonly sharedBuffer = new SharedArrayBuffer(2 * Float64Array.BYTES_PER_ELEMENT);\n private readonly view = new Float64Array(this.sharedBuffer);\n private timer: NodeJS.Timeout | null = null;\n private maxLagMs = 0;\n\n start(): void {\n if (this.timer) return;\n this.view[0] = Date.now();\n this.timer = setInterval(() => {\n const now = Date.now();\n const lag = Math.max(0, now - this.view[0] - LOOP_TICK_INTERVAL_MS);\n if (lag >= LOOP_STALL_LOG_MS) {\n this.maxLagMs = Math.max(this.maxLagMs, lag);\n process.stderr.write(\n `[conveyor-agent] event-loop stall: ${Math.round(lag)}ms (${new Date().toISOString()})\\n`,\n );\n }\n this.view[0] = now;\n }, LOOP_TICK_INTERVAL_MS);\n // Never keep the process alive just to measure it.\n this.timer.unref();\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n /** Mirror the runner's current status for the worker to report. */\n setStatus(status: LoopStatus): void {\n const idx = LOOP_STATUS_VALUES.indexOf(status);\n this.view[1] = idx === -1 ? 0 : idx;\n }\n\n /** Worst stall observed since the previous call (then reset). Includes the\n * live lag so a beat sent mid-stall from this thread — possible when the\n * heartbeat timer fires before the refresher in the same tick batch — is\n * still truthful. */\n takeMaxLagMs(): number {\n const max = Math.max(this.maxLagMs, lagFromBuffer(this.view));\n this.maxLagMs = 0;\n return max;\n }\n}\n"],"mappings":";AAiBO,IAAM,wBAAwB;AAE9B,IAAM,oBAAoB;AAE1B,IAAM,qBAAqB,CAAC,UAAU,QAAQ,UAAU;AAKxD,SAAS,cAAc,MAA4B;AACxD,QAAM,OAAO,KAAK,CAAC;AACnB,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,qBAAqB;AAC9D;AAEO,SAAS,iBAAiB,MAAgC;AAC/D,SAAO,mBAAmB,KAAK,CAAC,CAAC,KAAK;AACxC;AAEO,IAAM,iBAAN,MAAqB;AAAA,EACjB,eAAe,IAAI,kBAAkB,IAAI,aAAa,iBAAiB;AAAA,EAC/D,OAAO,IAAI,aAAa,KAAK,YAAY;AAAA,EAClD,QAA+B;AAAA,EAC/B,WAAW;AAAA,EAEnB,QAAc;AACZ,QAAI,KAAK,MAAO;AAChB,SAAK,KAAK,CAAC,IAAI,KAAK,IAAI;AACxB,SAAK,QAAQ,YAAY,MAAM;AAC7B,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,MAAM,KAAK,IAAI,GAAG,MAAM,KAAK,KAAK,CAAC,IAAI,qBAAqB;AAClE,UAAI,OAAO,mBAAmB;AAC5B,aAAK,WAAW,KAAK,IAAI,KAAK,UAAU,GAAG;AAC3C,gBAAQ,OAAO;AAAA,UACb,sCAAsC,KAAK,MAAM,GAAG,CAAC,QAAO,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AAAA,QACtF;AAAA,MACF;AACA,WAAK,KAAK,CAAC,IAAI;AAAA,IACjB,GAAG,qBAAqB;AAExB,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,MAAO,eAAc,KAAK,KAAK;AACxC,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,UAAU,QAA0B;AAClC,UAAM,MAAM,mBAAmB,QAAQ,MAAM;AAC7C,SAAK,KAAK,CAAC,IAAI,QAAQ,KAAK,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAuB;AACrB,UAAM,MAAM,KAAK,IAAI,KAAK,UAAU,cAAc,KAAK,IAAI,CAAC;AAC5D,SAAK,WAAW;AAChB,WAAO;AAAA,EACT;AACF;","names":[]}