@camstack/addon-pipeline 1.2.219 → 1.2.220
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/audio-analyzer/index.js +49 -5
- package/dist/audio-analyzer/index.mjs +46 -3
- package/dist/chunk-emK7D4bc.js +35 -0
- package/dist/{default-detection-model-Dsk1a5pY.mjs → default-detection-model-DBNEc3iw.mjs} +1 -1
- package/dist/{default-detection-model-d7-Q_LMo.js → default-detection-model-x6oNMBDH.js} +1 -1
- package/dist/detection-pipeline/index.js +10 -9
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-wtNhtVVo.js → dist-ByPsWlTa.js} +2 -37
- package/dist/{dist-CKK0CBUW.mjs → dist-ClihWGOZ.mjs} +2 -2
- package/dist/{lazy-sharp-zoQ07I43.js → lazy-sharp-BuwKlQLR.js} +2 -2
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-BduNi21Z.js → node-CNPnaux4.js} +4 -3
- package/dist/{node-CrNBWFbA.mjs → node-DiaTmz84.mjs} +1 -1
- package/dist/pipeline-runner/index.js +222 -157
- package/dist/pipeline-runner/index.mjs +218 -154
- package/dist/{process-memory-Dwl0MFPH.js → process-memory-9KqRtUHt.js} +3 -2
- package/dist/{process-memory-BRQOtDN1.mjs → process-memory-D4t8hpJd.mjs} +1 -1
- package/dist/recorder/index.js +334 -123
- package/dist/recorder/index.mjs +332 -122
- package/dist/{segment-demux-js-D2o9ShNL.mjs → segment-demux-js-CFFXVae4.mjs} +1 -1
- package/dist/{segment-demux-js-CFOU3biG.js → segment-demux-js-CZefXDLx.js} +4 -3
- package/dist/session-decode/decode-worker-child.js +155 -74
- package/dist/session-decode/decode-worker-child.mjs +154 -73
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DN_IoShh.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CtdpN7Sz.mjs} +3 -3
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-n7c9pNlt.mjs → hostInit-McoUiljf.mjs} +3 -3
- package/dist/stream-broker/index.js +24 -25
- package/dist/stream-broker/index.mjs +17 -19
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/worker-protocol-B2MfQLlu.js +184 -0
- package/dist/worker-protocol-C_W-P_g-.mjs +161 -0
- package/package.json +1 -1
- package/dist/worker-protocol-Cx3Nqreq.mjs +0 -264
- package/dist/worker-protocol-iWTFnnUM.js +0 -299
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $ as failureContributionCapability, It as errMsg, Qt as nodePin, R as customAction, U as deriveDetailCropRect, Ut as BaseAddon, V as defineCustomActions, _ as FailureCounters, _t as pickDetailCropConvention, an as boolean, bt as pipelineRunnerCapability, cn as literal, hn as EventCategory, in as array, it as loadContributionCapability, ln as number, m as DEVICE_BACKEND_TO_FORMAT, p as DEFAULT_NATIVE_LEASE_SETTINGS, pn as string, qt as createEvent, rn as _enum, sn as lazy, u as DEFAULT_DETAIL_CROP_CONVENTION, un as object, vt as pickNativeLeaseOverride } from "../dist-
|
|
2
|
-
import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-
|
|
3
|
-
import { i as resolveFrameViewGeometry, l as getStepDefinition, r as localFrameRegistry } from "../default-detection-model-
|
|
4
|
-
import {
|
|
1
|
+
import { $ as failureContributionCapability, It as errMsg, Qt as nodePin, R as customAction, U as deriveDetailCropRect, Ut as BaseAddon, V as defineCustomActions, _ as FailureCounters, _t as pickDetailCropConvention, an as boolean, b as NativeLeaseSettingsSchema, bt as pipelineRunnerCapability, cn as literal, hn as EventCategory, in as array, it as loadContributionCapability, ln as number, m as DEVICE_BACKEND_TO_FORMAT, p as DEFAULT_NATIVE_LEASE_SETTINGS, pn as string, qt as createEvent, rn as _enum, sn as lazy, u as DEFAULT_DETAIL_CROP_CONVENTION, un as object, vt as pickNativeLeaseOverride, y as NativeLeaseAdmissionSchema } from "../dist-ClihWGOZ.mjs";
|
|
2
|
+
import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-DiaTmz84.mjs";
|
|
3
|
+
import { i as resolveFrameViewGeometry, l as getStepDefinition, r as localFrameRegistry } from "../default-detection-model-DBNEc3iw.mjs";
|
|
4
|
+
import { t as isWorkerReply } from "../worker-protocol-C_W-P_g-.mjs";
|
|
5
5
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
6
6
|
import { t as getSharp } from "../lazy-sharp-DXsqwpph.mjs";
|
|
7
7
|
import { a as preRollRequestOf, o as withDetectionIntent, t as DETECTION_PRE_ROLL_REQUEST_MS } from "../restream-intent-DWe2kInY.mjs";
|
|
@@ -67,26 +67,23 @@ function wrapChild(child, onExit) {
|
|
|
67
67
|
* signature (it forwards the same source in the subsequent `start` message);
|
|
68
68
|
* the child entry itself is source-independent, so it is not consumed here.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
70
|
+
* The fork carries NO configuration. Until D453 it spread a set of
|
|
71
|
+
* `*_SETTING` env vars in, because the child resolved its native-lease knobs
|
|
72
|
+
* at module load; the knobs now travel on the `start` message
|
|
73
|
+
* (`WorkerRequest.lease`), resolved in this process. That is what makes a
|
|
74
|
+
* decode worker forkable BEFORE anyone knows which camera it will serve — a
|
|
75
|
+
* child configured at spawn is a child bound to the configuration it was born
|
|
76
|
+
* with — and it is what let the child bundle drop the Zod schemas that cost
|
|
77
|
+
* every fork ~170 ms and ~100 MB of heap (D453).
|
|
78
78
|
*
|
|
79
79
|
* The inherited environment is SPREAD, never replaced: this child resolves
|
|
80
80
|
* node-av through `CAMSTACK_FRAMEWORK_DIR` / `CAMSTACK_BUNDLED_ADDONS_DIR` on
|
|
81
81
|
* agents, so a replaced env would silently break decoding off the hub.
|
|
82
82
|
*/
|
|
83
|
-
function forkDecodeWorker(source,
|
|
83
|
+
function forkDecodeWorker(source, costRegistry) {
|
|
84
84
|
const child = fork(CHILD_ENTRY_PATH, [], {
|
|
85
85
|
serialization: "advanced",
|
|
86
|
-
env: {
|
|
87
|
-
...process.env,
|
|
88
|
-
...nativeLeaseSettingEnv(leaseOverride)
|
|
89
|
-
}
|
|
86
|
+
env: { ...process.env }
|
|
90
87
|
});
|
|
91
88
|
const costClaim = costRegistry?.claim({
|
|
92
89
|
role: "decode",
|
|
@@ -97,6 +94,145 @@ function forkDecodeWorker(source, leaseOverride = {}, costRegistry) {
|
|
|
97
94
|
return wrapChild(child, () => costClaim.release());
|
|
98
95
|
}
|
|
99
96
|
//#endregion
|
|
97
|
+
//#region src/session-decode/native-lease-config.ts
|
|
98
|
+
/**
|
|
99
|
+
* Resolution of the native-frame hold / tile knobs for one decode session —
|
|
100
|
+
* PARENT side. The result travels to the decode worker on its `start` message
|
|
101
|
+
* (`native-lease-knobs.ts` is the wire half, and the only half the child sees).
|
|
102
|
+
*
|
|
103
|
+
* ## Where the values come from
|
|
104
|
+
*
|
|
105
|
+
* The knobs are cluster-wide `pipeline-orchestrator` settings
|
|
106
|
+
* (`@camstack/types` → `pipeline/native-lease.ts`). The pipeline-runner reads
|
|
107
|
+
* them over the hub-routed `addon-settings` cap
|
|
108
|
+
* (`pipeline-runner/native-lease-settings-source.ts`) and resolves them HERE,
|
|
109
|
+
* once per session, at the moment the session starts — so a changed setting
|
|
110
|
+
* reaches the next session and leaves running ones alone, exactly as before.
|
|
111
|
+
*
|
|
112
|
+
* Until D453 the resolution ran inside the CHILD, from `*_SETTING` env vars the
|
|
113
|
+
* parent wrote at fork time. That bound a worker to the knobs it was forked
|
|
114
|
+
* with (a pre-forked worker cannot be), and it dragged the Zod schemas — the
|
|
115
|
+
* whole `@camstack/types` index — into the child bundle: ~130 ms and ~140 MB
|
|
116
|
+
* per fork, measured. Moving the resolution up one process removed both.
|
|
117
|
+
*
|
|
118
|
+
* ## Precedence — setting > env > default
|
|
119
|
+
*
|
|
120
|
+
* 1. **setting** — the operator set the knob in the orchestrator's global
|
|
121
|
+
* settings. Wins, because a value someone deliberately chose in the UI must
|
|
122
|
+
* not be silently overruled by a shell variable nobody can see from there.
|
|
123
|
+
* 2. **env** — {@link NATIVE_LEASE_ENV_VARS}, the pre-settings mechanism, kept
|
|
124
|
+
* as an emergency / dev override. It applies only while the corresponding
|
|
125
|
+
* setting is UNSET. "Unset" includes a knob sitting AT its shipped default:
|
|
126
|
+
* `addon-settings.getGlobalSettings` hydrates an unstored field with the
|
|
127
|
+
* schema default and offers no way to tell the two apart, so treating the
|
|
128
|
+
* default as a choice would have retired every env var on the day this
|
|
129
|
+
* shipped. Move the slider off the default and the setting takes over; move
|
|
130
|
+
* it back and the env var returns. See `readNativeLeaseOverride`.
|
|
131
|
+
* 3. **default** — {@link DEFAULT_NATIVE_LEASE_SETTINGS}, byte-for-byte the
|
|
132
|
+
* constants the worker shipped with.
|
|
133
|
+
*
|
|
134
|
+
* A value that fails its bounds at ANY level falls through to the next one
|
|
135
|
+
* rather than being clamped: clamping turns a typo into a number nobody chose,
|
|
136
|
+
* and this one governs how much RAM a busy camera holds.
|
|
137
|
+
*
|
|
138
|
+
* The GPU surface ring (`CAMSTACK_SESSION_NATIVE_CROP_RING`) is deliberately
|
|
139
|
+
* NOT here — see its docblock in `decode-worker-child.ts` for why it stays
|
|
140
|
+
* env-only.
|
|
141
|
+
*/
|
|
142
|
+
/** Emergency / dev override env vars (pre-settings; lowest precedence but the default). */
|
|
143
|
+
var NATIVE_LEASE_ENV_VARS = {
|
|
144
|
+
holdFrames: "CAMSTACK_SESSION_NATIVE_LEASE_HOLD_FRAMES",
|
|
145
|
+
budgetMb: "CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB",
|
|
146
|
+
activityMs: "CAMSTACK_SESSION_NATIVE_LEASE_ACTIVITY_MS",
|
|
147
|
+
admission: "CAMSTACK_SESSION_NATIVE_LEASE_ADMISSION",
|
|
148
|
+
tileBudgetMb: "CAMSTACK_SESSION_NATIVE_TILE_BUDGET_MB",
|
|
149
|
+
sceneBudgetMb: "CAMSTACK_SESSION_NATIVE_SCENE_BUDGET_MB"
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Validate one candidate through the knob's own Zod bound. `null` for absent,
|
|
153
|
+
* non-numeric, or out-of-bounds input — the caller then falls through to the
|
|
154
|
+
* next precedence level.
|
|
155
|
+
*/
|
|
156
|
+
function parseKnob(knob, raw) {
|
|
157
|
+
if (raw === void 0) return null;
|
|
158
|
+
if (typeof raw === "string" && raw.trim() === "") return null;
|
|
159
|
+
const parsed = NativeLeaseSettingsSchema.shape[knob].safeParse(Number(raw));
|
|
160
|
+
return parsed.success ? parsed.data : null;
|
|
161
|
+
}
|
|
162
|
+
/** {@link parseKnob} for the one knob whose value is a word, not a number. */
|
|
163
|
+
function parseAdmission(raw) {
|
|
164
|
+
if (raw === void 0) return null;
|
|
165
|
+
const parsed = NativeLeaseAdmissionSchema.safeParse(raw.trim());
|
|
166
|
+
return parsed.success ? parsed.data : null;
|
|
167
|
+
}
|
|
168
|
+
/** Resolve one NUMERIC knob across the three precedence levels. */
|
|
169
|
+
function resolveKnob(knob, env, override) {
|
|
170
|
+
const fromSetting = parseKnob(knob, override[knob]);
|
|
171
|
+
if (fromSetting !== null) return {
|
|
172
|
+
value: fromSetting,
|
|
173
|
+
source: "setting"
|
|
174
|
+
};
|
|
175
|
+
const fromEnv = parseKnob(knob, env[NATIVE_LEASE_ENV_VARS[knob]]);
|
|
176
|
+
if (fromEnv !== null) return {
|
|
177
|
+
value: fromEnv,
|
|
178
|
+
source: "env"
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
value: DEFAULT_NATIVE_LEASE_SETTINGS[knob],
|
|
182
|
+
source: "default"
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** {@link resolveKnob} for `admission`. Same precedence, different parser. */
|
|
186
|
+
function resolveAdmission(env, override) {
|
|
187
|
+
const fromSetting = parseAdmission(override.admission);
|
|
188
|
+
if (fromSetting !== null) return {
|
|
189
|
+
value: fromSetting,
|
|
190
|
+
source: "setting"
|
|
191
|
+
};
|
|
192
|
+
const fromEnv = parseAdmission(env[NATIVE_LEASE_ENV_VARS.admission]);
|
|
193
|
+
if (fromEnv !== null) return {
|
|
194
|
+
value: fromEnv,
|
|
195
|
+
source: "env"
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
value: DEFAULT_NATIVE_LEASE_SETTINGS.admission,
|
|
199
|
+
source: "default"
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Resolve every lease knob for one session. Pure.
|
|
204
|
+
*
|
|
205
|
+
* `override` carries ONLY the knobs the operator actually SET (see
|
|
206
|
+
* `native-lease-settings-source.ts`); an empty object leaves the env override
|
|
207
|
+
* fully functional, which is the whole point of the distinction.
|
|
208
|
+
*/
|
|
209
|
+
function resolveNativeLeaseKnobs(env, override) {
|
|
210
|
+
const holdFrames = resolveKnob("holdFrames", env, override);
|
|
211
|
+
const budgetMb = resolveKnob("budgetMb", env, override);
|
|
212
|
+
const activityMs = resolveKnob("activityMs", env, override);
|
|
213
|
+
const admission = resolveAdmission(env, override);
|
|
214
|
+
const tileBudgetMb = resolveKnob("tileBudgetMb", env, override);
|
|
215
|
+
const sceneBudgetMb = resolveKnob("sceneBudgetMb", env, override);
|
|
216
|
+
return {
|
|
217
|
+
values: {
|
|
218
|
+
holdFrames: holdFrames.value,
|
|
219
|
+
budgetMb: budgetMb.value,
|
|
220
|
+
activityMs: activityMs.value,
|
|
221
|
+
admission: admission.value,
|
|
222
|
+
tileBudgetMb: tileBudgetMb.value,
|
|
223
|
+
sceneBudgetMb: sceneBudgetMb.value
|
|
224
|
+
},
|
|
225
|
+
sources: {
|
|
226
|
+
holdFrames: holdFrames.source,
|
|
227
|
+
budgetMb: budgetMb.source,
|
|
228
|
+
activityMs: activityMs.source,
|
|
229
|
+
admission: admission.source,
|
|
230
|
+
tileBudgetMb: tileBudgetMb.source,
|
|
231
|
+
sceneBudgetMb: sceneBudgetMb.source
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
100
236
|
//#region src/session-decode/guarded-session-decode.ts
|
|
101
237
|
/**
|
|
102
238
|
* 2s → 5s → 15s → 30s, then 30s forever.
|
|
@@ -1343,11 +1479,12 @@ var SessionDecodeSession = class {
|
|
|
1343
1479
|
this.child.on("message", (m) => this.handleMessage(m));
|
|
1344
1480
|
this.child.on("exit", (m) => this.handleExit(toWorkerExitInfo(m)));
|
|
1345
1481
|
}
|
|
1346
|
-
start(source, opts) {
|
|
1482
|
+
start(source, opts, lease) {
|
|
1347
1483
|
this.trySend({
|
|
1348
1484
|
kind: "start",
|
|
1349
1485
|
source,
|
|
1350
|
-
opts
|
|
1486
|
+
opts,
|
|
1487
|
+
lease
|
|
1351
1488
|
});
|
|
1352
1489
|
}
|
|
1353
1490
|
/**
|
|
@@ -1515,6 +1652,7 @@ var SessionDecodeSession = class {
|
|
|
1515
1652
|
...raw.reason !== void 0 ? { workerReason: raw.reason } : {}
|
|
1516
1653
|
});
|
|
1517
1654
|
return;
|
|
1655
|
+
case "ready": return;
|
|
1518
1656
|
case "error":
|
|
1519
1657
|
this.settleError(raw.message, raw.frameId);
|
|
1520
1658
|
return;
|
|
@@ -1766,7 +1904,7 @@ function createSessionDecodeCoordinator(deps) {
|
|
|
1766
1904
|
const firstFrameMaxWaitMs = Math.max(deps.firstFrameMaxWaitMs ?? DEFAULT_FIRST_FRAME_MAX_WAIT_MS, firstFrameTimeoutMs);
|
|
1767
1905
|
return { async *generateVideoFrames(source, opts) {
|
|
1768
1906
|
const session = new SessionDecodeSession(deps.fork(source), deps.logger, source.deviceId, firstFrameTimeoutMs, frameTimeoutMs, firstFrameMaxWaitMs, deps.onDialTiming, deps.onPreRollReport);
|
|
1769
|
-
session.start(source, opts);
|
|
1907
|
+
session.start(source, opts, deps.lease());
|
|
1770
1908
|
try {
|
|
1771
1909
|
while (true) {
|
|
1772
1910
|
const outcome = await session.pullNext();
|
|
@@ -1833,6 +1971,7 @@ function startSessionDecodePump(deps) {
|
|
|
1833
1971
|
let dialTiming = null;
|
|
1834
1972
|
const iterator = createSessionDecodeCoordinator({
|
|
1835
1973
|
fork: deps.fork,
|
|
1974
|
+
lease: deps.lease,
|
|
1836
1975
|
logger: deps.logger,
|
|
1837
1976
|
onDialTiming: (timing) => {
|
|
1838
1977
|
dialTiming = timing;
|
|
@@ -6548,7 +6687,6 @@ function writeRetainedDetectionFrame(store, deviceId, frame, retention) {
|
|
|
6548
6687
|
if (!isContinuousRetainEnabled()) return store.allocateHandle(deviceId, frame) ?? void 0;
|
|
6549
6688
|
return store.write(deviceId, frame, frame.frameRef ? createRetainedContentFallback(localFrameRegistry, frame.frameRef) : void 0) ?? void 0;
|
|
6550
6689
|
}
|
|
6551
|
-
var DETECTION_KEEPWARM_MS = 15e3;
|
|
6552
6690
|
/**
|
|
6553
6691
|
* Occupancy-burst safety timeout (both the dial path and the analyzer-tap
|
|
6554
6692
|
* path — Task 10). Bounds worst-case burst latency when frames stop
|
|
@@ -6575,6 +6713,31 @@ var DETECTION_REDIAL_DELAY_MS = 2e3;
|
|
|
6575
6713
|
*/
|
|
6576
6714
|
var DETECTION_REDIAL_GIVEUP_WINDOW_MS = 6e4;
|
|
6577
6715
|
/**
|
|
6716
|
+
* ## There is no decode keep-warm any more — D453 removed it
|
|
6717
|
+
*
|
|
6718
|
+
* A `WarmDetachedDecode` used to hold a detection worker alive for 15 s across
|
|
6719
|
+
* a detach, hoping the next motion event would adopt it. It did not park: it
|
|
6720
|
+
* kept its broker restream lease and kept DECODING, because the child's loop is
|
|
6721
|
+
* `for await (... decoder.frames(demuxer.packets(...)))`, driven by the demuxer
|
|
6722
|
+
* and not by the parent's `pull` — `minIntervalMs` throttles only inside
|
|
6723
|
+
* `FrameSlot.publish`, after the decode. `framesDecoded` advanced in 122 of 122
|
|
6724
|
+
* park windows at full source rate, and a decode-worker child costs 40-80% of a
|
|
6725
|
+
* core.
|
|
6726
|
+
*
|
|
6727
|
+
* So the park cost about what running costs, and it bought a re-fork back in
|
|
6728
|
+
* 12% of cases: on this fleet, 272 of 440 park windows expired having adopted
|
|
6729
|
+
* nothing, while the two decode-worker children stood at 319% of the 522% of
|
|
6730
|
+
* CPU CamStack drew on the host. D282 put the arithmetic at ~25x net-negative
|
|
6731
|
+
* against the forks it avoided, and D453 made the fork itself ~150 ms and
|
|
6732
|
+
* ~100 MB cheaper, which only widens it.
|
|
6733
|
+
*
|
|
6734
|
+
* Do not reintroduce a keep-warm shaped like this one, and do not "fix" it with
|
|
6735
|
+
* a longer TTL: the measured p50 gap between two sessions of one camera is
|
|
6736
|
+
* 61 s, so a longer window scales the waste and not the hit rate. If a warm
|
|
6737
|
+
* process is wanted, it must be a process that holds NO stream and decodes
|
|
6738
|
+
* NOTHING — see D453 for what that costs and why it was not taken either.
|
|
6739
|
+
*/
|
|
6740
|
+
/**
|
|
6578
6741
|
* Pure decision helper: returns `true` when a dynamic frame-diff analyzer
|
|
6579
6742
|
* subscription should be opened for an onboard-motion camera.
|
|
6580
6743
|
*
|
|
@@ -6900,12 +7063,6 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
6900
7063
|
retention = null;
|
|
6901
7064
|
attached = /* @__PURE__ */ new Map();
|
|
6902
7065
|
/**
|
|
6903
|
-
* Detection decode workers parked across a detach, keyed by deviceId — see
|
|
6904
|
-
* {@link WarmDetachedDecode}. Bounded by construction: at most one entry per
|
|
6905
|
-
* camera, each self-expiring after {@link DETECTION_KEEPWARM_MS}.
|
|
6906
|
-
*/
|
|
6907
|
-
warmDetachedDetection = /* @__PURE__ */ new Map();
|
|
6908
|
-
/**
|
|
6909
7066
|
* Detail step state parked across a SOFT detach, keyed by deviceId — see
|
|
6910
7067
|
* `detail-park.ts` for the fault this closes. Bounded by construction: at
|
|
6911
7068
|
* most one entry per camera, each self-expiring after
|
|
@@ -7264,14 +7421,9 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
7264
7421
|
this.runner = null;
|
|
7265
7422
|
}
|
|
7266
7423
|
for (const attachment of this.attached.values()) {
|
|
7267
|
-
if (attachment.detectionKeepWarmTimer !== null) {
|
|
7268
|
-
clearTimeout(attachment.detectionKeepWarmTimer);
|
|
7269
|
-
attachment.detectionKeepWarmTimer = null;
|
|
7270
|
-
}
|
|
7271
7424
|
attachment.motionUnsubscribe?.();
|
|
7272
7425
|
attachment.detectionUnsubscribe?.();
|
|
7273
7426
|
}
|
|
7274
|
-
for (const deviceId of [...this.warmDetachedDetection.keys()]) this.dropWarmDetectionDecode(deviceId, "shutdown");
|
|
7275
7427
|
for (const deviceId of [...this.parkedDetail.keys()]) this.dropParkedDetail(deviceId);
|
|
7276
7428
|
this.attached.clear();
|
|
7277
7429
|
this.lastMotionFrameAt.clear();
|
|
@@ -7982,7 +8134,6 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
7982
8134
|
config,
|
|
7983
8135
|
motionUnsubscribe: null,
|
|
7984
8136
|
detectionUnsubscribe: null,
|
|
7985
|
-
detectionKeepWarmTimer: null,
|
|
7986
8137
|
detailSteps: deriveDetailSteps(config.steps)
|
|
7987
8138
|
};
|
|
7988
8139
|
this.attached.set(config.deviceId, attachment);
|
|
@@ -8638,12 +8789,24 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
8638
8789
|
warn: (message, extras) => this.ctxIfReady?.logger.warn(message, extras)
|
|
8639
8790
|
} });
|
|
8640
8791
|
/**
|
|
8641
|
-
* Fork a decode worker
|
|
8642
|
-
*
|
|
8643
|
-
*
|
|
8792
|
+
* Fork a decode worker. Used in place of a bare `forkDecodeWorker` reference
|
|
8793
|
+
* at every session-decode spawn site so the cost registry is never forgotten.
|
|
8794
|
+
*
|
|
8795
|
+
* The fork carries NO configuration: since D453 the lease knobs travel on the
|
|
8796
|
+
* `start` message ({@link resolveSessionLeaseKnobs}), not in the child's
|
|
8797
|
+
* environment. A worker is therefore forkable before anyone knows which
|
|
8798
|
+
* camera it will serve.
|
|
8799
|
+
*/
|
|
8800
|
+
forkDecodeWorkerWithLease = (source) => forkDecodeWorker(source, this.costRegistry);
|
|
8801
|
+
/**
|
|
8802
|
+
* The lease knobs for the session that is about to start — `setting > env >
|
|
8803
|
+
* default`, resolved HERE because this process is the one that carries the
|
|
8804
|
+
* Zod schemas (the child no longer does; D453).
|
|
8805
|
+
*
|
|
8806
|
+
* Read per session, so a changed operator setting reaches NEW sessions and
|
|
8644
8807
|
* leaves running ones alone (a live session would have to be re-dialled).
|
|
8645
8808
|
*/
|
|
8646
|
-
|
|
8809
|
+
resolveSessionLeaseKnobs = () => resolveNativeLeaseKnobs(process.env, this.nativeLease.current());
|
|
8647
8810
|
/**
|
|
8648
8811
|
* Refresh the lease knobs before a decode worker is spawned. Awaited on the
|
|
8649
8812
|
* `acquire` leg of `startGuardedSessionDecode` (which always completes before
|
|
@@ -8848,19 +9011,10 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
8848
9011
|
*/
|
|
8849
9012
|
handleDetectionDecodeEnded(deviceId) {
|
|
8850
9013
|
const attachment = this.attached.get(deviceId);
|
|
8851
|
-
if (!attachment)
|
|
8852
|
-
this.dropWarmDetectionDecode(deviceId, "worker-died-while-parked");
|
|
8853
|
-
return;
|
|
8854
|
-
}
|
|
9014
|
+
if (!attachment) return;
|
|
8855
9015
|
const log = this.ctx.logger.withTags({ deviceId });
|
|
8856
9016
|
attachment.detectionUnsubscribe?.();
|
|
8857
9017
|
attachment.detectionUnsubscribe = null;
|
|
8858
|
-
if (attachment.detectionKeepWarmTimer !== null) {
|
|
8859
|
-
clearTimeout(attachment.detectionKeepWarmTimer);
|
|
8860
|
-
attachment.detectionKeepWarmTimer = null;
|
|
8861
|
-
log.warn("detection decode worker died while kept warm — next open re-forks cold");
|
|
8862
|
-
return;
|
|
8863
|
-
}
|
|
8864
9018
|
const now = Date.now();
|
|
8865
9019
|
const last = this.lastDecodeUnexpectedEndAt.get(deviceId) ?? 0;
|
|
8866
9020
|
this.lastDecodeUnexpectedEndAt.set(deviceId, now);
|
|
@@ -8885,13 +9039,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
8885
9039
|
/**
|
|
8886
9040
|
* Tear a camera down.
|
|
8887
9041
|
*
|
|
8888
|
-
* `
|
|
8889
|
-
*
|
|
8890
|
-
*
|
|
8891
|
-
*
|
|
8892
|
-
*
|
|
8893
|
-
*
|
|
8894
|
-
*
|
|
9042
|
+
* `softDetach` is the {@link detachCamera} cap entry point (the
|
|
9043
|
+
* orchestrator's motion-cooldown teardown). It keeps the DETAIL step tree
|
|
9044
|
+
* parked — a config slice, no process, no CPU — so a track outliving this
|
|
9045
|
+
* session can still resolve its enrichment steps. It does NOT keep the decode
|
|
9046
|
+
* worker: since D453 every detach releases it, because a held worker went on
|
|
9047
|
+
* decoding at full rate for the whole window (see the note above
|
|
9048
|
+
* {@link AttachedCamera}). Every internal detach — an attach replacing a
|
|
9049
|
+
* stale config, a decode that died twice, shutdown — is hard on both counts.
|
|
8895
9050
|
*/
|
|
8896
9051
|
/**
|
|
8897
9052
|
* Park the detaching camera's detail-relevant config slice for the grace
|
|
@@ -8938,18 +9093,13 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
8938
9093
|
}
|
|
8939
9094
|
return parked.state;
|
|
8940
9095
|
}
|
|
8941
|
-
detachInternal(deviceId,
|
|
9096
|
+
detachInternal(deviceId, softDetach = false) {
|
|
8942
9097
|
const attachment = this.attached.get(deviceId);
|
|
8943
9098
|
if (!attachment) return;
|
|
8944
9099
|
this.clearOnboardAnalyzer(deviceId);
|
|
8945
|
-
if (attachment.detectionKeepWarmTimer !== null) {
|
|
8946
|
-
clearTimeout(attachment.detectionKeepWarmTimer);
|
|
8947
|
-
attachment.detectionKeepWarmTimer = null;
|
|
8948
|
-
}
|
|
8949
9100
|
attachment.motionUnsubscribe?.();
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
if (keepDecodeWarm) this.parkDetailState(deviceId, attachment.config);
|
|
9101
|
+
attachment.detectionUnsubscribe?.();
|
|
9102
|
+
if (softDetach) this.parkDetailState(deviceId, attachment.config);
|
|
8953
9103
|
else this.dropParkedDetail(deviceId);
|
|
8954
9104
|
this.lastMotionFrameAt.delete(deviceId);
|
|
8955
9105
|
this.lastDecodeUnexpectedEndAt.delete(deviceId);
|
|
@@ -8974,83 +9124,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
8974
9124
|
this.runner?.unregisterCamera(deviceId);
|
|
8975
9125
|
this.ctx?.logger.info("detachCamera", {
|
|
8976
9126
|
tags: { deviceId },
|
|
8977
|
-
meta: {
|
|
8978
|
-
});
|
|
8979
|
-
}
|
|
8980
|
-
/**
|
|
8981
|
-
* The decode-relevant identity of a camera's detection decode. Two configs
|
|
8982
|
-
* that agree on all of it can share one worker; anything else must re-fork.
|
|
8983
|
-
* Kept in ONE place so a future decode-shaping option cannot be added to the
|
|
8984
|
-
* pump without the adoption check noticing.
|
|
8985
|
-
*/
|
|
8986
|
-
detectionDecodeIdentity(config) {
|
|
8987
|
-
return {
|
|
8988
|
-
streamId: config.detectionStreamId,
|
|
8989
|
-
fps: config.detectionFps,
|
|
8990
|
-
hostname: resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]),
|
|
8991
|
-
ownerNodeId: resolveSessionDecodeOwnerNodeId(config)
|
|
8992
|
-
};
|
|
8993
|
-
}
|
|
8994
|
-
/**
|
|
8995
|
-
* Hold a detached camera's detection decode worker for
|
|
8996
|
-
* {@link DETECTION_KEEPWARM_MS} so the next motion event reuses it.
|
|
8997
|
-
* Replacing an existing park releases the older worker first — two parked
|
|
8998
|
-
* workers for one camera would be two live decodes and two broker leases.
|
|
8999
|
-
*/
|
|
9000
|
-
parkWarmDetectionDecode(deviceId, config, unsubscribe) {
|
|
9001
|
-
this.dropWarmDetectionDecode(deviceId, "replaced");
|
|
9002
|
-
const timer = setTimeout(() => {
|
|
9003
|
-
this.warmDetachedDetection.delete(deviceId);
|
|
9004
|
-
unsubscribe();
|
|
9005
|
-
this.ctx?.logger.info("session-decode: warm detection decode expired — worker released", {
|
|
9006
|
-
tags: { deviceId },
|
|
9007
|
-
meta: { keepWarmMs: DETECTION_KEEPWARM_MS }
|
|
9008
|
-
});
|
|
9009
|
-
}, DETECTION_KEEPWARM_MS);
|
|
9010
|
-
timer.unref?.();
|
|
9011
|
-
this.warmDetachedDetection.set(deviceId, {
|
|
9012
|
-
unsubscribe,
|
|
9013
|
-
timer,
|
|
9014
|
-
...this.detectionDecodeIdentity(config)
|
|
9015
|
-
});
|
|
9016
|
-
}
|
|
9017
|
-
/**
|
|
9018
|
-
* Adopt a parked decode worker for a camera that is attaching again, or
|
|
9019
|
-
* `null` when there is nothing to adopt.
|
|
9020
|
-
*
|
|
9021
|
-
* A parked worker whose identity no longer matches is DROPPED here rather
|
|
9022
|
-
* than left to expire: the caller is about to fork a replacement, and letting
|
|
9023
|
-
* both run would double this camera's decode cost for the rest of the window.
|
|
9024
|
-
*/
|
|
9025
|
-
adoptWarmDetectionDecode(deviceId, config) {
|
|
9026
|
-
const warm = this.warmDetachedDetection.get(deviceId);
|
|
9027
|
-
if (!warm) return null;
|
|
9028
|
-
const wanted = this.detectionDecodeIdentity(config);
|
|
9029
|
-
if (warm.streamId !== wanted.streamId || warm.fps !== wanted.fps || warm.hostname !== wanted.hostname || warm.ownerNodeId !== wanted.ownerNodeId) {
|
|
9030
|
-
this.dropWarmDetectionDecode(deviceId, "config-changed");
|
|
9031
|
-
return null;
|
|
9032
|
-
}
|
|
9033
|
-
clearTimeout(warm.timer);
|
|
9034
|
-
this.warmDetachedDetection.delete(deviceId);
|
|
9035
|
-
this.ctx?.logger.info("session-decode: reusing the warm detection decode worker (no re-fork)", {
|
|
9036
|
-
tags: { deviceId },
|
|
9037
|
-
meta: {
|
|
9038
|
-
streamId: warm.streamId,
|
|
9039
|
-
fps: warm.fps
|
|
9040
|
-
}
|
|
9041
|
-
});
|
|
9042
|
-
return warm.unsubscribe;
|
|
9043
|
-
}
|
|
9044
|
-
/** Release a parked worker now. `reason` names why in the log. */
|
|
9045
|
-
dropWarmDetectionDecode(deviceId, reason) {
|
|
9046
|
-
const warm = this.warmDetachedDetection.get(deviceId);
|
|
9047
|
-
if (!warm) return;
|
|
9048
|
-
clearTimeout(warm.timer);
|
|
9049
|
-
this.warmDetachedDetection.delete(deviceId);
|
|
9050
|
-
warm.unsubscribe();
|
|
9051
|
-
this.ctx?.logger.info("session-decode: warm detection decode released", {
|
|
9052
|
-
tags: { deviceId },
|
|
9053
|
-
meta: { reason }
|
|
9127
|
+
meta: { detailParked: this.parkedDetail.has(deviceId) }
|
|
9054
9128
|
});
|
|
9055
9129
|
}
|
|
9056
9130
|
/**
|
|
@@ -9361,6 +9435,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
9361
9435
|
let reservation = null;
|
|
9362
9436
|
return startSessionDecodePump({
|
|
9363
9437
|
fork: this.forkDecodeWorkerWithLease,
|
|
9438
|
+
lease: this.resolveSessionLeaseKnobs,
|
|
9364
9439
|
logger: log,
|
|
9365
9440
|
source,
|
|
9366
9441
|
opts: {
|
|
@@ -9519,6 +9594,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
9519
9594
|
} });
|
|
9520
9595
|
return startSessionDecodePump({
|
|
9521
9596
|
fork: this.forkDecodeWorkerWithLease,
|
|
9597
|
+
lease: this.resolveSessionLeaseKnobs,
|
|
9522
9598
|
logger: log,
|
|
9523
9599
|
source,
|
|
9524
9600
|
opts: {
|
|
@@ -9565,16 +9641,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
9565
9641
|
const attachment = this.attached.get(deviceId);
|
|
9566
9642
|
if (!attachment) return;
|
|
9567
9643
|
if (action === "subscribe") {
|
|
9568
|
-
if (attachment.detectionKeepWarmTimer !== null) {
|
|
9569
|
-
clearTimeout(attachment.detectionKeepWarmTimer);
|
|
9570
|
-
attachment.detectionKeepWarmTimer = null;
|
|
9571
|
-
}
|
|
9572
9644
|
if (attachment.detectionUnsubscribe !== null) return;
|
|
9573
|
-
const adopted = this.adoptWarmDetectionDecode(deviceId, attachment.config);
|
|
9574
|
-
if (adopted) {
|
|
9575
|
-
attachment.detectionUnsubscribe = adopted;
|
|
9576
|
-
return;
|
|
9577
|
-
}
|
|
9578
9645
|
this.subscribeDetectionFrames(attachment.config).then((unsub) => {
|
|
9579
9646
|
if (!this.attached.has(deviceId)) {
|
|
9580
9647
|
unsub?.();
|
|
@@ -9583,13 +9650,9 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
9583
9650
|
attachment.detectionUnsubscribe = unsub;
|
|
9584
9651
|
});
|
|
9585
9652
|
} else {
|
|
9586
|
-
if (attachment.detectionKeepWarmTimer !== null) return;
|
|
9587
9653
|
if (attachment.detectionUnsubscribe === null) return;
|
|
9588
|
-
attachment.
|
|
9589
|
-
|
|
9590
|
-
attachment.detectionUnsubscribe?.();
|
|
9591
|
-
attachment.detectionUnsubscribe = null;
|
|
9592
|
-
}, DETECTION_KEEPWARM_MS);
|
|
9654
|
+
attachment.detectionUnsubscribe();
|
|
9655
|
+
attachment.detectionUnsubscribe = null;
|
|
9593
9656
|
}
|
|
9594
9657
|
}
|
|
9595
9658
|
/**
|
|
@@ -10341,6 +10404,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
|
|
|
10341
10404
|
const useLocalFrameRef = this.executorCoLocated() && process.env["CAMSTACK_DISABLE_LOCAL_FRAME_REF"] !== "1";
|
|
10342
10405
|
return startSessionDecodePump({
|
|
10343
10406
|
fork: this.forkDecodeWorkerWithLease,
|
|
10407
|
+
lease: this.resolveSessionLeaseKnobs,
|
|
10344
10408
|
logger: log,
|
|
10345
10409
|
source,
|
|
10346
10410
|
opts: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_chunk = require("./chunk-emK7D4bc.js");
|
|
2
|
+
const require_dist = require("./dist-ByPsWlTa.js");
|
|
2
3
|
let node_fs = require("node:fs");
|
|
3
|
-
node_fs =
|
|
4
|
+
node_fs = require_chunk.__toESM(node_fs);
|
|
4
5
|
let node_child_process = require("node:child_process");
|
|
5
6
|
//#region src/shared/node-topology-platform.ts
|
|
6
7
|
/**
|