@crouton-kit/crouter 0.3.64 → 0.3.65
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.
|
@@ -44,7 +44,21 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
44
44
|
import { join } from 'node:path';
|
|
45
45
|
import { createHarness } from '../helpers/harness.js';
|
|
46
46
|
import { subscribe } from '../../canvas/canvas.js';
|
|
47
|
-
import { isPidAlive } from '../../canvas/pid.js';
|
|
47
|
+
import { isPidAlive, capturePidIdentities } from '../../canvas/pid.js';
|
|
48
|
+
// Diagnostic-only, gated behind CRTR_DEBUG_PID_LIVENESS (same env var as the
|
|
49
|
+
// isRecordedPidAlive trace in pid.ts): logs whether the SIGKILLed pid reads
|
|
50
|
+
// alive and what identity ps reports for it, right at the two moments that
|
|
51
|
+
// matter for the intermittent Linux-only crash-revive wedge — directly after
|
|
52
|
+
// confirming the kill and again just before the grace-elapsed tick — so a real
|
|
53
|
+
// run shows whether the pid was reused during the grace window. No effect on
|
|
54
|
+
// test behavior when the env var is unset.
|
|
55
|
+
function debugLogPidState(label, pid) {
|
|
56
|
+
if (!process.env.CRTR_DEBUG_PID_LIVENESS)
|
|
57
|
+
return;
|
|
58
|
+
const identities = capturePidIdentities([pid]);
|
|
59
|
+
const identity = identities === null ? 'probe-null' : identities.has(pid) ? JSON.stringify(identities.get(pid)) : 'no-entry';
|
|
60
|
+
process.stderr.write(`[broker-crash-teardown] ${label}: pid=${pid} isPidAlive=${isPidAlive(pid)} capturePidIdentities=${identity}\n`);
|
|
61
|
+
}
|
|
48
62
|
// crtrd.ts module const (not exported): the fresh-pi-boot grace window the daemon
|
|
49
63
|
// waits before grace-reviving / boot-failing a pi observed dead. Reference:
|
|
50
64
|
// crtrd.ts `REVIVE_GRACE_MS = 20_000`.
|
|
@@ -81,9 +95,11 @@ test('CRASH → grace-revive RESUME (one-writer), then clean teardown on close',
|
|
|
81
95
|
// Actions run: crouter-development/prove-ci-fixes-in-actions). The waitFor's
|
|
82
96
|
// successful resolution already IS the proof of deadness at this point.
|
|
83
97
|
await h.waitFor(() => !isPidAlive(oldPid), { label: 'crashed broker pid is dead' });
|
|
98
|
+
debugLogPidState('after waitFor(!isPidAlive(oldPid))', oldPid);
|
|
84
99
|
const NOW = 5_000_000;
|
|
85
100
|
await h.tick(NOW); // pid dead, intent null → handleBrokerLiveness → handleLiveWindow marks pending
|
|
86
101
|
assert.equal(h.bootCount(id), boots, 'inside the grace window → NOT yet revived');
|
|
102
|
+
debugLogPidState('before grace-elapsed tick', oldPid);
|
|
87
103
|
await h.tick(NOW + REVIVE_GRACE_MS + 1); // grace elapsed → reviveNode(resume:true)
|
|
88
104
|
// This is the file's SECOND real broker boot (crash-revive), on an
|
|
89
105
|
// oversubscribed CI runner sharing the host with the other full-tier files'
|
package/dist/core/canvas/pid.js
CHANGED
|
@@ -74,17 +74,62 @@ export function isPidAlive(pid) {
|
|
|
74
74
|
* discipline: a MISMATCH is the ONLY thing that reads dead, never an absence of
|
|
75
75
|
* evidence. */
|
|
76
76
|
export function isRecordedPidAlive(pid, expectedIdentity) {
|
|
77
|
-
|
|
77
|
+
const debug = Boolean(process.env.CRTR_DEBUG_PID_LIVENESS);
|
|
78
|
+
const alive = isPidAlive(pid);
|
|
79
|
+
if (!alive) {
|
|
80
|
+
if (debug)
|
|
81
|
+
logPidLivenessDecision(pid, alive, expectedIdentity, 'no-probe', 'isPidAlive=false → dead', false);
|
|
78
82
|
return false; // gone/zombie → dead (unchanged path)
|
|
79
|
-
|
|
83
|
+
}
|
|
84
|
+
if (expectedIdentity == null) {
|
|
85
|
+
if (debug)
|
|
86
|
+
logPidLivenessDecision(pid, alive, expectedIdentity, 'no-probe', 'no baseline → trust isPidAlive', true);
|
|
80
87
|
return true; // no baseline → trust isPidAlive (legacy behavior)
|
|
88
|
+
}
|
|
81
89
|
const current = capturePidIdentities([pid]);
|
|
82
|
-
if (current == null)
|
|
90
|
+
if (current == null) {
|
|
91
|
+
if (debug)
|
|
92
|
+
logPidLivenessDecision(pid, alive, expectedIdentity, 'probe-null', 'ps probe failed → fail-open alive', true);
|
|
83
93
|
return true; // probe itself failed → fail-open
|
|
94
|
+
}
|
|
84
95
|
const actual = current.get(pid);
|
|
85
|
-
if (actual === undefined)
|
|
96
|
+
if (actual === undefined) {
|
|
97
|
+
if (debug)
|
|
98
|
+
logPidLivenessDecision(pid, alive, expectedIdentity, 'no-entry', 'no identity row for pid (race) → fail-open alive', true);
|
|
86
99
|
return true; // present but no identity row (race) → fail-open
|
|
87
|
-
|
|
100
|
+
}
|
|
101
|
+
const result = actual === expectedIdentity; // different identity → REUSED → dead
|
|
102
|
+
if (debug)
|
|
103
|
+
logPidLivenessDecision(pid, alive, expectedIdentity, 'value', result ? 'identity matches expected → alive' : 'identity differs from expected → REUSED → dead', result, actual);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
/** Diagnostic-only decision trace for `isRecordedPidAlive`, gated entirely
|
|
107
|
+
* behind `CRTR_DEBUG_PID_LIVENESS` — callers must check the env var
|
|
108
|
+
* themselves before calling this so the (tiny) formatting cost is paid ONLY
|
|
109
|
+
* when the env var is set; this function does no env check of its own.
|
|
110
|
+
* Written to trace the intermittent Linux-only CI crash-revive wedge
|
|
111
|
+
* (`broker-crash-teardown.test.ts`'s 'CRASH → grace-revive RESUME'): the
|
|
112
|
+
* goal is for a single stderr line per call to make BOTH a passing run ("pid
|
|
113
|
+
* read dead → revive") and a wedged run ("read alive via <branch>") fully
|
|
114
|
+
* self-explaining, without changing any return value. `capturePidIdentities`
|
|
115
|
+
* outcome is reported as one of three DISTINCT cases — `probe-null` (the
|
|
116
|
+
* `ps` probe itself failed), `no-entry` (probe succeeded but the map has no
|
|
117
|
+
* row for this pid), or `value` (probe succeeded and found an identity,
|
|
118
|
+
* printed verbatim) — plus `no-probe` for the two branches that return
|
|
119
|
+
* before `capturePidIdentities` is ever called. Kept as permanent
|
|
120
|
+
* observability for this primitive, not a throwaway debug print: pid-reuse
|
|
121
|
+
* liveness is subtle enough that the next person chasing a wedge here should
|
|
122
|
+
* find this already in place. */
|
|
123
|
+
function logPidLivenessDecision(pid, isPidAliveResult, expectedIdentity, probeOutcome, branch, returned, actualIdentity) {
|
|
124
|
+
const parts = [
|
|
125
|
+
`pid=${String(pid)}`,
|
|
126
|
+
`isPidAlive=${isPidAliveResult}`,
|
|
127
|
+
`expectedIdentity=${expectedIdentity === undefined ? 'undefined' : expectedIdentity === null ? 'null' : JSON.stringify(expectedIdentity)}`,
|
|
128
|
+
`capturePidIdentities=${probeOutcome}${probeOutcome === 'value' ? `(${JSON.stringify(actualIdentity)})` : ''}`,
|
|
129
|
+
`branch=${JSON.stringify(branch)}`,
|
|
130
|
+
`returned=${returned}`,
|
|
131
|
+
];
|
|
132
|
+
process.stderr.write(`[isRecordedPidAlive] ${parts.join(' ')}\n`);
|
|
88
133
|
}
|
|
89
134
|
/** SIGTERM a process GROUP by pid (the negative-pid convention) — best-effort,
|
|
90
135
|
* swallowing ESRCH (already gone). Shared by triggers.ts (cancel-time reap of
|