@camstack/addon-pipeline 1.2.262 → 1.2.263

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.
@@ -11928,6 +11928,31 @@ function pendingMovesSignature(moves) {
11928
11928
  return moves.map((m) => `${String(m.deviceId)}:${m.profile}>${m.plannedFor}`).join(",");
11929
11929
  }
11930
11930
  //#endregion
11931
+ //#region src/recorder/addon/hour-adoption-catchup.ts
11932
+ /**
11933
+ * @returns true when the run stopped with work still to do — the caller should
11934
+ * come back SOON rather than at the next daily tick. A repair that knows it
11935
+ * is unfinished and waits a day is how an archive stays short for months.
11936
+ */
11937
+ async function runAdoptionCatchUp(deps) {
11938
+ const startedAt = deps.now();
11939
+ const spent = () => deps.now() - startedAt;
11940
+ for (const locationId of deps.locationIds) for (;;) {
11941
+ if (spent() >= deps.budgetMs) return true;
11942
+ let report;
11943
+ try {
11944
+ report = await deps.sweep({ locationId });
11945
+ } catch (error) {
11946
+ deps.onError?.(locationId, error);
11947
+ break;
11948
+ }
11949
+ if (!report.bounded) break;
11950
+ if (spent() >= deps.budgetMs) return true;
11951
+ await deps.pace();
11952
+ }
11953
+ return false;
11954
+ }
11955
+ //#endregion
11931
11956
  //#region src/recorder/addon/hour-adoption-sweep.ts
11932
11957
  var HOUR_MS$1 = 36e5;
11933
11958
  /** The profile trees a camera directory is allowed to have. Everything else
@@ -12196,12 +12221,16 @@ function startPeriodicPass(deps) {
12196
12221
  };
12197
12222
  const timer = setInterval(fire, deps.intervalMs);
12198
12223
  timer.unref?.();
12224
+ const firstRun = deps.firstRunDelayMs === void 0 ? null : setTimeout(fire, deps.firstRunDelayMs);
12225
+ firstRun?.unref?.();
12199
12226
  return {
12200
12227
  stop() {
12201
12228
  stopped = true;
12202
12229
  clearInterval(timer);
12230
+ if (firstRun !== null) clearTimeout(firstRun);
12203
12231
  },
12204
- isRunning: () => startedAt !== null
12232
+ isRunning: () => startedAt !== null,
12233
+ fireNow: fire
12205
12234
  };
12206
12235
  }
12207
12236
  //#endregion
@@ -17793,6 +17822,29 @@ var LEDGER_WALK_PAUSE_MS = 100;
17793
17822
  * are already contending for that array.
17794
17823
  */
17795
17824
  var HOUR_ADOPTION_INTERVAL_MS = 1440 * 6e4;
17825
+ /**
17826
+ * How long after boot the first adoption pass runs.
17827
+ *
17828
+ * Long enough that it is not competing with everything else a starting recorder
17829
+ * does (locations resolving, writers attaching, the ledger warming), short
17830
+ * enough that a hub restarted twice in an evening still repairs its archive.
17831
+ */
17832
+ var HOUR_ADOPTION_FIRST_RUN_DELAY_MS = 3 * 6e4;
17833
+ /**
17834
+ * How long one adoption run may keep sweeping bounded locations.
17835
+ *
17836
+ * Long enough to make real progress on a damaged archive (the first pass here
17837
+ * moved ~29 k segments in about twenty seconds of listings), short enough that
17838
+ * the array is not being walked continuously — every listing is a `readdir` on
17839
+ * a spinning disk shared with live writers.
17840
+ */
17841
+ var HOUR_ADOPTION_CATCHUP_BUDGET_MS = 10 * 6e4;
17842
+ /**
17843
+ * How long the recorder waits before resuming an adoption run that stopped on
17844
+ * its budget. Enough of a gap that the array is not walked back-to-back;
17845
+ * nothing like a day.
17846
+ */
17847
+ var HOUR_ADOPTION_RESUME_MS = 5 * 6e4;
17796
17848
  /** An adoption pass longer than this is reported with its duration. Bounded
17797
17849
  * work (512 hour listings, paced) — minutes is normal, an hour is not. */
17798
17850
  var HOUR_ADOPTION_SLOW_AFTER_MS = 10 * 6e4;
@@ -18021,6 +18073,7 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
18021
18073
  * the storage record no longer has. Armed in `onHubReachable`. */
18022
18074
  ghostLocationPass = null;
18023
18075
  hourAdoptionPass = null;
18076
+ hourAdoptionResume;
18024
18077
  /** Reversible migration pause lease; never reflected in RecordingConfig. */
18025
18078
  storageMigrationLeaseId = null;
18026
18079
  /**
@@ -18920,9 +18973,30 @@ var RecorderV2Addon = class extends require_dist.BaseAddon {
18920
18973
  this.hourAdoptionPass = startPeriodicPass({
18921
18974
  name: "hour-adoption",
18922
18975
  intervalMs: HOUR_ADOPTION_INTERVAL_MS,
18976
+ firstRunDelayMs: HOUR_ADOPTION_FIRST_RUN_DELAY_MS,
18923
18977
  slowAfterMs: HOUR_ADOPTION_SLOW_AFTER_MS,
18924
18978
  run: async () => {
18925
- for (const location of this.resolvedLocations) await adopter.sweep({ locationId: location.id });
18979
+ if (await runAdoptionCatchUp({
18980
+ locationIds: this.resolvedLocations.map((location) => location.id),
18981
+ sweep: (input) => adopter.sweep(input),
18982
+ budgetMs: HOUR_ADOPTION_CATCHUP_BUDGET_MS,
18983
+ now: () => Date.now(),
18984
+ pace: () => require_dist.sleep(LEDGER_WALK_PAUSE_MS),
18985
+ onError: (locationId, error) => {
18986
+ this.ctx.logger.warn("recorder: hour adoption could not sweep a location", { meta: {
18987
+ locationId,
18988
+ error: require_dist.errMsg(error)
18989
+ } });
18990
+ }
18991
+ })) {
18992
+ this.ctx.logger.info("recorder: hour adoption stopped on its budget with work left — resuming shortly", { meta: { resumeInMs: HOUR_ADOPTION_RESUME_MS } });
18993
+ this.hourAdoptionResume?.unref?.();
18994
+ clearTimeout(this.hourAdoptionResume);
18995
+ this.hourAdoptionResume = setTimeout(() => {
18996
+ this.hourAdoptionPass?.fireNow();
18997
+ }, HOUR_ADOPTION_RESUME_MS);
18998
+ this.hourAdoptionResume.unref?.();
18999
+ }
18926
19000
  },
18927
19001
  logger: this.ctx.logger
18928
19002
  });
@@ -11921,6 +11921,31 @@ function pendingMovesSignature(moves) {
11921
11921
  return moves.map((m) => `${String(m.deviceId)}:${m.profile}>${m.plannedFor}`).join(",");
11922
11922
  }
11923
11923
  //#endregion
11924
+ //#region src/recorder/addon/hour-adoption-catchup.ts
11925
+ /**
11926
+ * @returns true when the run stopped with work still to do — the caller should
11927
+ * come back SOON rather than at the next daily tick. A repair that knows it
11928
+ * is unfinished and waits a day is how an archive stays short for months.
11929
+ */
11930
+ async function runAdoptionCatchUp(deps) {
11931
+ const startedAt = deps.now();
11932
+ const spent = () => deps.now() - startedAt;
11933
+ for (const locationId of deps.locationIds) for (;;) {
11934
+ if (spent() >= deps.budgetMs) return true;
11935
+ let report;
11936
+ try {
11937
+ report = await deps.sweep({ locationId });
11938
+ } catch (error) {
11939
+ deps.onError?.(locationId, error);
11940
+ break;
11941
+ }
11942
+ if (!report.bounded) break;
11943
+ if (spent() >= deps.budgetMs) return true;
11944
+ await deps.pace();
11945
+ }
11946
+ return false;
11947
+ }
11948
+ //#endregion
11924
11949
  //#region src/recorder/addon/hour-adoption-sweep.ts
11925
11950
  var HOUR_MS$1 = 36e5;
11926
11951
  /** The profile trees a camera directory is allowed to have. Everything else
@@ -12189,12 +12214,16 @@ function startPeriodicPass(deps) {
12189
12214
  };
12190
12215
  const timer = setInterval(fire, deps.intervalMs);
12191
12216
  timer.unref?.();
12217
+ const firstRun = deps.firstRunDelayMs === void 0 ? null : setTimeout(fire, deps.firstRunDelayMs);
12218
+ firstRun?.unref?.();
12192
12219
  return {
12193
12220
  stop() {
12194
12221
  stopped = true;
12195
12222
  clearInterval(timer);
12223
+ if (firstRun !== null) clearTimeout(firstRun);
12196
12224
  },
12197
- isRunning: () => startedAt !== null
12225
+ isRunning: () => startedAt !== null,
12226
+ fireNow: fire
12198
12227
  };
12199
12228
  }
12200
12229
  //#endregion
@@ -17786,6 +17815,29 @@ var LEDGER_WALK_PAUSE_MS = 100;
17786
17815
  * are already contending for that array.
17787
17816
  */
17788
17817
  var HOUR_ADOPTION_INTERVAL_MS = 1440 * 6e4;
17818
+ /**
17819
+ * How long after boot the first adoption pass runs.
17820
+ *
17821
+ * Long enough that it is not competing with everything else a starting recorder
17822
+ * does (locations resolving, writers attaching, the ledger warming), short
17823
+ * enough that a hub restarted twice in an evening still repairs its archive.
17824
+ */
17825
+ var HOUR_ADOPTION_FIRST_RUN_DELAY_MS = 3 * 6e4;
17826
+ /**
17827
+ * How long one adoption run may keep sweeping bounded locations.
17828
+ *
17829
+ * Long enough to make real progress on a damaged archive (the first pass here
17830
+ * moved ~29 k segments in about twenty seconds of listings), short enough that
17831
+ * the array is not being walked continuously — every listing is a `readdir` on
17832
+ * a spinning disk shared with live writers.
17833
+ */
17834
+ var HOUR_ADOPTION_CATCHUP_BUDGET_MS = 10 * 6e4;
17835
+ /**
17836
+ * How long the recorder waits before resuming an adoption run that stopped on
17837
+ * its budget. Enough of a gap that the array is not walked back-to-back;
17838
+ * nothing like a day.
17839
+ */
17840
+ var HOUR_ADOPTION_RESUME_MS = 5 * 6e4;
17789
17841
  /** An adoption pass longer than this is reported with its duration. Bounded
17790
17842
  * work (512 hour listings, paced) — minutes is normal, an hour is not. */
17791
17843
  var HOUR_ADOPTION_SLOW_AFTER_MS = 10 * 6e4;
@@ -18014,6 +18066,7 @@ var RecorderV2Addon = class extends BaseAddon {
18014
18066
  * the storage record no longer has. Armed in `onHubReachable`. */
18015
18067
  ghostLocationPass = null;
18016
18068
  hourAdoptionPass = null;
18069
+ hourAdoptionResume;
18017
18070
  /** Reversible migration pause lease; never reflected in RecordingConfig. */
18018
18071
  storageMigrationLeaseId = null;
18019
18072
  /**
@@ -18913,9 +18966,30 @@ var RecorderV2Addon = class extends BaseAddon {
18913
18966
  this.hourAdoptionPass = startPeriodicPass({
18914
18967
  name: "hour-adoption",
18915
18968
  intervalMs: HOUR_ADOPTION_INTERVAL_MS,
18969
+ firstRunDelayMs: HOUR_ADOPTION_FIRST_RUN_DELAY_MS,
18916
18970
  slowAfterMs: HOUR_ADOPTION_SLOW_AFTER_MS,
18917
18971
  run: async () => {
18918
- for (const location of this.resolvedLocations) await adopter.sweep({ locationId: location.id });
18972
+ if (await runAdoptionCatchUp({
18973
+ locationIds: this.resolvedLocations.map((location) => location.id),
18974
+ sweep: (input) => adopter.sweep(input),
18975
+ budgetMs: HOUR_ADOPTION_CATCHUP_BUDGET_MS,
18976
+ now: () => Date.now(),
18977
+ pace: () => sleep$1(LEDGER_WALK_PAUSE_MS),
18978
+ onError: (locationId, error) => {
18979
+ this.ctx.logger.warn("recorder: hour adoption could not sweep a location", { meta: {
18980
+ locationId,
18981
+ error: errMsg(error)
18982
+ } });
18983
+ }
18984
+ })) {
18985
+ this.ctx.logger.info("recorder: hour adoption stopped on its budget with work left — resuming shortly", { meta: { resumeInMs: HOUR_ADOPTION_RESUME_MS } });
18986
+ this.hourAdoptionResume?.unref?.();
18987
+ clearTimeout(this.hourAdoptionResume);
18988
+ this.hourAdoptionResume = setTimeout(() => {
18989
+ this.hourAdoptionPass?.fireNow();
18990
+ }, HOUR_ADOPTION_RESUME_MS);
18991
+ this.hourAdoptionResume.unref?.();
18992
+ }
18919
18993
  },
18920
18994
  logger: this.ctx.logger
18921
18995
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.262",
3
+ "version": "1.2.263",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",