@camstack/addon-pipeline 1.2.302 → 1.2.303
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.
- package/dist/pipeline-runner/index.js +211 -13
- package/dist/pipeline-runner/index.mjs +211 -13
- package/dist/recorder/index.js +1349 -70
- package/dist/recorder/index.mjs +1423 -145
- package/dist/session-decode/decode-worker-main.js +250 -57
- package/dist/session-decode/decode-worker-main.mjs +250 -57
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DFaPx_Ot.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CF7C68rV.mjs} +1 -1
- package/dist/stream-broker/{hostInit-CePd6XbV.mjs → hostInit-dac4-ofq.mjs} +1 -1
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -34,37 +34,131 @@ var OutboundSendTracker = class {
|
|
|
34
34
|
};
|
|
35
35
|
function createWorkerExit(deps) {
|
|
36
36
|
let exiting = false;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
let exited = false;
|
|
38
|
+
let exitCode = 0;
|
|
39
|
+
const finish = () => {
|
|
40
|
+
if (exited) return;
|
|
41
|
+
exited = true;
|
|
42
|
+
deps.exit(exitCode);
|
|
43
|
+
};
|
|
44
|
+
return (code, urgency = "flush") => {
|
|
45
|
+
if (exiting) {
|
|
46
|
+
if (urgency === "now") finish();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
39
49
|
exiting = true;
|
|
40
|
-
|
|
50
|
+
exitCode = code;
|
|
41
51
|
try {
|
|
42
52
|
deps.steps.quiesce();
|
|
43
53
|
} catch {
|
|
44
54
|
exitCode = 1;
|
|
45
|
-
} finally {
|
|
46
|
-
try {
|
|
47
|
-
deps.steps.flushFinalLines();
|
|
48
|
-
} catch {
|
|
49
|
-
exitCode = 1;
|
|
50
|
-
} finally {
|
|
51
|
-
deps.sends.whenIdle(deps.boundMs ?? 250).then(() => deps.exit(exitCode));
|
|
52
|
-
}
|
|
53
55
|
}
|
|
56
|
+
try {
|
|
57
|
+
deps.steps.flushFinalLines();
|
|
58
|
+
} catch {
|
|
59
|
+
exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
if (urgency === "now") {
|
|
62
|
+
finish();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
deps.sends.whenIdle(deps.boundMs ?? 250).then(finish);
|
|
54
66
|
};
|
|
55
67
|
}
|
|
56
68
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
69
|
+
* Reports every fault, rate-limited: the first {@link FAULT_REPORT_LIMIT} in
|
|
70
|
+
* each {@link FAULT_REPORT_WINDOW_MS} window go out, and the rest are counted
|
|
71
|
+
* and reported as one line by {@link flushSuppressed} at exit (D666).
|
|
72
|
+
*
|
|
73
|
+
* A report that throws is swallowed here. If it were rethrown, it would become
|
|
74
|
+
* the next uncaught error, and reporting that one would write again. That is
|
|
75
|
+
* the loop that kept the Mac agent's orphans spinning.
|
|
76
|
+
*/
|
|
77
|
+
var FaultReporter = class {
|
|
78
|
+
report;
|
|
79
|
+
reportSuppressed;
|
|
80
|
+
now;
|
|
81
|
+
windowStart = Number.NEGATIVE_INFINITY;
|
|
82
|
+
inWindow = 0;
|
|
83
|
+
suppressed = 0;
|
|
84
|
+
constructor(report, reportSuppressed, now = Date.now) {
|
|
85
|
+
this.report = report;
|
|
86
|
+
this.reportSuppressed = reportSuppressed;
|
|
87
|
+
this.now = now;
|
|
88
|
+
}
|
|
89
|
+
/** Report one fault unless the window is full; true when it was reported. */
|
|
90
|
+
fault(kind, err) {
|
|
91
|
+
const at = this.now();
|
|
92
|
+
if (at - this.windowStart >= 6e4) {
|
|
93
|
+
this.windowStart = at;
|
|
94
|
+
this.inWindow = 0;
|
|
95
|
+
}
|
|
96
|
+
if (this.inWindow >= 5) {
|
|
97
|
+
this.suppressed += 1;
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
this.inWindow += 1;
|
|
101
|
+
this.attempt(() => this.report(kind, err));
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
/** The count of faults not reported, as one line. Nothing when there were none. */
|
|
105
|
+
flushSuppressed() {
|
|
106
|
+
const count = this.suppressed;
|
|
107
|
+
if (count === 0) return;
|
|
108
|
+
this.suppressed = 0;
|
|
109
|
+
this.attempt(() => this.reportSuppressed(count));
|
|
110
|
+
}
|
|
111
|
+
attempt(write) {
|
|
112
|
+
try {
|
|
113
|
+
write();
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Route every event that ends the worker through the one exit (D652, D666):
|
|
119
|
+
*
|
|
120
|
+
* - SIGTERM (the parent's `kill()`) flushes, bounded; a SECOND SIGTERM exits now.
|
|
121
|
+
* - a lost channel, and a changed ppid, exit NOW — the parent is gone.
|
|
122
|
+
* - stdio lost is never an uncaught exception. While the channel is up it is
|
|
123
|
+
* NOT an exit: raw stderr is the secondary log path (the IPC copy is what
|
|
124
|
+
* reaches the runner's logger), so the worker says so once over IPC and
|
|
125
|
+
* keeps decoding; exiting would only make the runner re-dial into the same
|
|
126
|
+
* dead stderr. With the channel down too, it exits now.
|
|
127
|
+
* - an uncaught error is reported (rate-limited) and exits 1: flushed the
|
|
128
|
+
* first time, now for every later one — a process that keeps faulting must
|
|
129
|
+
* not keep formatting stacks.
|
|
130
|
+
* - an unhandled rejection is reported (rate-limited) and does not end the worker.
|
|
131
|
+
*
|
|
132
|
+
* When the parent is gone there is NO sink for the exit reason: stderr's
|
|
133
|
+
* reader died with it and the channel is closed. The exit is not logged, and
|
|
134
|
+
* must not try to be — trying is what looped.
|
|
61
135
|
*/
|
|
62
136
|
function wireWorkerExit(deps) {
|
|
63
|
-
|
|
64
|
-
deps.on("
|
|
137
|
+
let sigterms = 0;
|
|
138
|
+
deps.on("SIGTERM", () => {
|
|
139
|
+
sigterms += 1;
|
|
140
|
+
deps.exitWorker(0, sigterms > 1 ? "now" : "flush");
|
|
141
|
+
});
|
|
142
|
+
deps.on("disconnect", () => deps.exitWorker(0, "now"));
|
|
143
|
+
deps.on("parentChanged", () => deps.exitWorker(0, "now"));
|
|
144
|
+
let stdioLost = false;
|
|
145
|
+
deps.on("stdioLost", (err) => {
|
|
146
|
+
if (!stdioLost) {
|
|
147
|
+
stdioLost = true;
|
|
148
|
+
try {
|
|
149
|
+
deps.onStdioLost(err);
|
|
150
|
+
} catch {}
|
|
151
|
+
}
|
|
152
|
+
if (!deps.parentConnected()) deps.exitWorker(0, "now");
|
|
153
|
+
});
|
|
154
|
+
let uncaught = 0;
|
|
65
155
|
deps.on("uncaughtException", (err) => {
|
|
66
|
-
|
|
67
|
-
deps.
|
|
156
|
+
uncaught += 1;
|
|
157
|
+
deps.faults.fault("uncaughtException", err);
|
|
158
|
+
deps.exitWorker(1, uncaught > 1 ? "now" : "flush");
|
|
159
|
+
});
|
|
160
|
+
deps.on("unhandledRejection", (reason) => {
|
|
161
|
+
deps.faults.fault("unhandledRejection", reason);
|
|
68
162
|
});
|
|
69
163
|
}
|
|
70
164
|
//#endregion
|
|
@@ -2376,6 +2470,8 @@ var DecodeWorkerChild = class {
|
|
|
2376
2470
|
*/
|
|
2377
2471
|
native;
|
|
2378
2472
|
requestExit;
|
|
2473
|
+
/** Set once the inherited stderr failed; raw writes stop (D666). */
|
|
2474
|
+
stderrLost = false;
|
|
2379
2475
|
constructor(hooks = {}) {
|
|
2380
2476
|
this.requestExit = hooks.requestExit ?? (() => void 0);
|
|
2381
2477
|
this.native = hooks.disposer ?? new NativeDisposer((what) => this.emitStderr(`decode-worker-child: native dispose left to process exit — ${what}\n`, "debug"));
|
|
@@ -4230,7 +4326,11 @@ var DecodeWorkerChild = class {
|
|
|
4230
4326
|
*/
|
|
4231
4327
|
emitStderr(line, level) {
|
|
4232
4328
|
const tag = this.deviceId === null ? "[dev:?]" : `[dev:${this.deviceId}]`;
|
|
4233
|
-
|
|
4329
|
+
if (!this.stderrLost) try {
|
|
4330
|
+
process.stderr.write(`${tag} ${line}`);
|
|
4331
|
+
} catch {
|
|
4332
|
+
this.stderrLost = true;
|
|
4333
|
+
}
|
|
4234
4334
|
try {
|
|
4235
4335
|
this.send({
|
|
4236
4336
|
kind: "log",
|
|
@@ -4239,6 +4339,13 @@ var DecodeWorkerChild = class {
|
|
|
4239
4339
|
});
|
|
4240
4340
|
} catch {}
|
|
4241
4341
|
}
|
|
4342
|
+
/**
|
|
4343
|
+
* The inherited stderr failed (EPIPE — its reader died with the process that
|
|
4344
|
+
* held it). From here on {@link emitStderr} forwards over IPC only (D666).
|
|
4345
|
+
*/
|
|
4346
|
+
markStderrLost() {
|
|
4347
|
+
this.stderrLost = true;
|
|
4348
|
+
}
|
|
4242
4349
|
sendError(message, frameId) {
|
|
4243
4350
|
this.emitStderr(`${message}\n`);
|
|
4244
4351
|
this.send(frameId === void 0 ? {
|
|
@@ -4252,54 +4359,140 @@ var DecodeWorkerChild = class {
|
|
|
4252
4359
|
}
|
|
4253
4360
|
};
|
|
4254
4361
|
//#endregion
|
|
4362
|
+
//#region src/session-decode/decode-worker-process.ts
|
|
4363
|
+
/**
|
|
4364
|
+
* The decode worker's PROCESS wiring — everything that ties one
|
|
4365
|
+
* {@link DecodeWorkerChild} to the process it runs in, and the ONE exit every
|
|
4366
|
+
* ending goes through (D652, D666).
|
|
4367
|
+
*
|
|
4368
|
+
* Extracted from `decode-worker-main.ts` so a test fixture runs exactly the
|
|
4369
|
+
* code production runs: the orphan loop of D666 lived in this wiring, and the
|
|
4370
|
+
* unit tests that drove its pure pieces with fake timers and a fake `on` could
|
|
4371
|
+
* not see an event loop that never reaches its timers.
|
|
4372
|
+
*
|
|
4373
|
+
* ‼ SANCTIONED RAW IPC ‼ — `process.on('message')` / `process.send` are the
|
|
4374
|
+
* correct mechanism for this private, directly-forked worker only; see the
|
|
4375
|
+
* note at the top of `decode-worker-child.ts`.
|
|
4376
|
+
*/
|
|
4377
|
+
/**
|
|
4378
|
+
* How often the worker checks that its parent is still the process that forked
|
|
4379
|
+
* it. A parent whose IPC end outlives it would otherwise never `disconnect`.
|
|
4380
|
+
*/
|
|
4381
|
+
var DECODE_WORKER_PPID_CHECK_MS = 1e3;
|
|
4382
|
+
/** Stack lines carried by a fault report — enough to place it, bounded. */
|
|
4383
|
+
var FAULT_STACK_LINES = 8;
|
|
4384
|
+
/**
|
|
4385
|
+
* Call `onChanged` (once) when `proc.ppid` no longer matches its value now.
|
|
4386
|
+
* Compares against the ppid at start, never against 1: an orphan is adopted
|
|
4387
|
+
* by tini inside docker and may be adopted by a subreaper on macOS. The timer
|
|
4388
|
+
* is unref'd — it never keeps the worker alive on its own. Returns a stop.
|
|
4389
|
+
*/
|
|
4390
|
+
function watchParentPid(proc, onChanged, intervalMs = DECODE_WORKER_PPID_CHECK_MS) {
|
|
4391
|
+
const initialPpid = proc.ppid;
|
|
4392
|
+
const timer = setInterval(() => {
|
|
4393
|
+
const ppid = proc.ppid;
|
|
4394
|
+
if (ppid === initialPpid) return;
|
|
4395
|
+
clearInterval(timer);
|
|
4396
|
+
onChanged(ppid);
|
|
4397
|
+
}, intervalMs);
|
|
4398
|
+
timer.unref();
|
|
4399
|
+
return () => clearInterval(timer);
|
|
4400
|
+
}
|
|
4401
|
+
function errorCode(err) {
|
|
4402
|
+
if (err instanceof Error && "code" in err && typeof err.code === "string") return err.code;
|
|
4403
|
+
return errMessage(err);
|
|
4404
|
+
}
|
|
4405
|
+
/** The message, plus the first stack frames: the report is rate-limited, so it must place the fault. */
|
|
4406
|
+
function describeFault(err) {
|
|
4407
|
+
if (!(err instanceof Error) || err.stack === void 0) return errMessage(err);
|
|
4408
|
+
return err.stack.split("\n").slice(0, FAULT_STACK_LINES).join(" | ");
|
|
4409
|
+
}
|
|
4410
|
+
/**
|
|
4411
|
+
* Build the worker and wire `proc` to it: IPC messages, stdio loss, parent
|
|
4412
|
+
* loss, SIGTERM, uncaught errors and the ppid watchdog. Does NOT load native
|
|
4413
|
+
* modules — the entry calls `worker.warm()` itself.
|
|
4414
|
+
*/
|
|
4415
|
+
function wireDecodeWorkerProcess(proc) {
|
|
4416
|
+
const worker = new DecodeWorkerChild({ requestExit: (code) => exitWorker(code) });
|
|
4417
|
+
proc.on("message", (message) => {
|
|
4418
|
+
worker.handleMessage(message);
|
|
4419
|
+
});
|
|
4420
|
+
const faults = new FaultReporter((kind, err) => {
|
|
4421
|
+
const what = kind === "uncaughtException" ? "uncaught exception" : "unhandled rejection";
|
|
4422
|
+
worker.emitStderr(`decode-worker-child: ${what} — ${describeFault(err)}\n`);
|
|
4423
|
+
}, (count) => {
|
|
4424
|
+
worker.emitStderr(`decode-worker-child: ${count} further fault report(s) suppressed\n`);
|
|
4425
|
+
});
|
|
4426
|
+
/**
|
|
4427
|
+
* quiesce (the disposer starts refusing, so nothing native is freed under a
|
|
4428
|
+
* live read), queue the last lines, then exit — after a bounded wait for
|
|
4429
|
+
* those lines while the parent is alive, in the same turn once it is gone.
|
|
4430
|
+
*/
|
|
4431
|
+
const exitWorker = createWorkerExit({
|
|
4432
|
+
sends: worker.outbound,
|
|
4433
|
+
steps: {
|
|
4434
|
+
quiesce: () => worker.quiesceForExit(),
|
|
4435
|
+
flushFinalLines: () => {
|
|
4436
|
+
faults.flushSuppressed();
|
|
4437
|
+
worker.flushFinalLines();
|
|
4438
|
+
}
|
|
4439
|
+
},
|
|
4440
|
+
exit: (code) => proc.exit(code)
|
|
4441
|
+
});
|
|
4442
|
+
wireWorkerExit({
|
|
4443
|
+
on: (event, listener) => {
|
|
4444
|
+
switch (event) {
|
|
4445
|
+
case "SIGTERM":
|
|
4446
|
+
proc.on("SIGTERM", () => listener(void 0));
|
|
4447
|
+
return;
|
|
4448
|
+
case "disconnect":
|
|
4449
|
+
proc.on("disconnect", () => listener(void 0));
|
|
4450
|
+
return;
|
|
4451
|
+
case "stdioLost":
|
|
4452
|
+
proc.stdout.on("error", (err) => listener(err));
|
|
4453
|
+
proc.stderr.on("error", (err) => listener(err));
|
|
4454
|
+
return;
|
|
4455
|
+
case "parentChanged":
|
|
4456
|
+
watchParentPid(proc, (ppid) => listener(ppid));
|
|
4457
|
+
return;
|
|
4458
|
+
case "uncaughtException":
|
|
4459
|
+
proc.on("uncaughtException", (err) => listener(err));
|
|
4460
|
+
return;
|
|
4461
|
+
case "unhandledRejection":
|
|
4462
|
+
proc.on("unhandledRejection", (reason) => listener(reason));
|
|
4463
|
+
return;
|
|
4464
|
+
}
|
|
4465
|
+
},
|
|
4466
|
+
exitWorker,
|
|
4467
|
+
faults,
|
|
4468
|
+
onStdioLost: (err) => {
|
|
4469
|
+
worker.markStderrLost();
|
|
4470
|
+
worker.emitStderr(`decode-worker-child: stdio lost (${errorCode(err)}) — raw stderr off, IPC logging continues\n`, "warn");
|
|
4471
|
+
},
|
|
4472
|
+
parentConnected: () => proc.connected
|
|
4473
|
+
});
|
|
4474
|
+
return {
|
|
4475
|
+
worker,
|
|
4476
|
+
exitWorker
|
|
4477
|
+
};
|
|
4478
|
+
}
|
|
4479
|
+
//#endregion
|
|
4255
4480
|
//#region src/session-decode/decode-worker-main.ts
|
|
4256
4481
|
/**
|
|
4257
4482
|
* Decode-worker — the process ENTRY (its own vite build entry, the path
|
|
4258
4483
|
* `fork-decode-worker.ts` forks). The worker itself is `DecodeWorkerChild` in
|
|
4259
|
-
* `decode-worker-child.ts`;
|
|
4260
|
-
*
|
|
4261
|
-
*
|
|
4484
|
+
* `decode-worker-child.ts`; its process wiring — IPC messages and the ONE exit
|
|
4485
|
+
* every ending goes through (D652, D666) — is `decode-worker-process.ts`, so a
|
|
4486
|
+
* test fixture runs the same wiring. This file adds only what needs native
|
|
4487
|
+
* modules or the sharp loader.
|
|
4262
4488
|
*
|
|
4263
4489
|
* ‼ SANCTIONED RAW IPC ‼ — `process.on('message')` / `process.send` are the
|
|
4264
4490
|
* correct mechanism for this private, directly-forked worker only; see the
|
|
4265
4491
|
* note at the top of `decode-worker-child.ts`.
|
|
4266
4492
|
*/
|
|
4267
|
-
var
|
|
4493
|
+
var { worker } = wireDecodeWorkerProcess(process);
|
|
4268
4494
|
require_lazy_sharp.setSharpWarnSink((message) => {
|
|
4269
4495
|
worker.emitStderr(`decode-worker-child: ${message}\n`, "warn");
|
|
4270
4496
|
});
|
|
4271
|
-
process.on("message", (message) => {
|
|
4272
|
-
worker.handleMessage(message);
|
|
4273
|
-
});
|
|
4274
4497
|
worker.warm();
|
|
4275
|
-
/**
|
|
4276
|
-
* The ONE way this worker exits: quiesce (the disposer starts refusing, so
|
|
4277
|
-
* nothing native is freed under a live read), queue the last lines, wait —
|
|
4278
|
-
* bounded — for them to leave, exit. `stop`, SIGTERM (the parent's `kill()`),
|
|
4279
|
-
* a lost channel and an uncaught error all land here, and a step that throws
|
|
4280
|
-
* still exits (with code 1). Before D652 there was no SIGTERM handler and a
|
|
4281
|
-
* `kill()` ended the process with those lines still queued.
|
|
4282
|
-
*/
|
|
4283
|
-
var exitWorker = createWorkerExit({
|
|
4284
|
-
sends: worker.outbound,
|
|
4285
|
-
steps: {
|
|
4286
|
-
quiesce: () => worker.quiesceForExit(),
|
|
4287
|
-
flushFinalLines: () => worker.flushFinalLines()
|
|
4288
|
-
},
|
|
4289
|
-
exit: (code) => process.exit(code)
|
|
4290
|
-
});
|
|
4291
|
-
wireWorkerExit({
|
|
4292
|
-
on: (event, listener) => {
|
|
4293
|
-
if (event === "SIGTERM") process.on("SIGTERM", () => listener(void 0));
|
|
4294
|
-
else if (event === "disconnect") process.on("disconnect", () => listener(void 0));
|
|
4295
|
-
else process.on("uncaughtException", (err) => listener(err));
|
|
4296
|
-
},
|
|
4297
|
-
exitWorker,
|
|
4298
|
-
reportUncaught: (err) => {
|
|
4299
|
-
worker.emitStderr(`decode-worker-child: uncaught exception — ${errMessage(err)}\n`);
|
|
4300
|
-
}
|
|
4301
|
-
});
|
|
4302
|
-
process.on("unhandledRejection", (reason) => {
|
|
4303
|
-
worker.emitStderr(`decode-worker-child: unhandled rejection — ${errMessage(reason)}\n`);
|
|
4304
|
-
});
|
|
4305
4498
|
//#endregion
|