@camstack/addon-pipeline 1.1.23 → 1.1.24

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.
@@ -8915,6 +8915,13 @@ var DECODE_FPS_WINDOW_MS = 2e3;
8915
8915
  */
8916
8916
  var SESSION_LINGER_MS = 45e3;
8917
8917
  /**
8918
+ * Default retry cooldown after a decoder session lands on a NON-LOCAL node
8919
+ * (see `FrameHandlePlaneOptions.sessionRetryCooldownMs`). Long enough to
8920
+ * turn a per-packet storm into a slow probe; short enough that the plane
8921
+ * recovers within seconds once the local decoder respawns.
8922
+ */
8923
+ var SESSION_RETRY_COOLDOWN_MS = 1e4;
8924
+ /**
8918
8925
  * Owns the broker's shared-memory frame-handle subscriptions and the
8919
8926
  * per-format `frameSink: 'shm'` decoder sessions that feed them.
8920
8927
  */
@@ -8923,8 +8930,12 @@ var FrameHandlePlane = class {
8923
8930
  logger;
8924
8931
  resolveStreamInfo;
8925
8932
  localNodeId;
8933
+ sessionRetryCooldownMs;
8926
8934
  subscriptions = /* @__PURE__ */ new Map();
8927
8935
  sessions = /* @__PURE__ */ new Map();
8936
+ /** Per-format retry gate set when a session lands on a non-local node:
8937
+ * `ensureSession` skips creation until the epoch-ms deadline passes. */
8938
+ sessionRetryCooldownUntil = /* @__PURE__ */ new Map();
8928
8939
  /** In-flight `ensureSession` creation per format. Coalesces concurrent
8929
8940
  * same-format callers onto ONE decoder session — without it two callers
8930
8941
  * both pass the `sessions.get` check, both `createSession` (two ffmpeg),
@@ -8944,6 +8955,7 @@ var FrameHandlePlane = class {
8944
8955
  this.logger = options.logger;
8945
8956
  this.resolveStreamInfo = options.resolveStreamInfo;
8946
8957
  this.localNodeId = options.localNodeId;
8958
+ this.sessionRetryCooldownMs = options.sessionRetryCooldownMs ?? SESSION_RETRY_COOLDOWN_MS;
8947
8959
  }
8948
8960
  /** Number of active handle subscriptions — surfaced for diagnostics. */
8949
8961
  get subscriberCount() {
@@ -8977,8 +8989,13 @@ var FrameHandlePlane = class {
8977
8989
  nodeId: session.nodeId
8978
8990
  } });
8979
8991
  await this.destroySession(session);
8980
- await this.startSessionDecoder(session);
8981
- rotated += 1;
8992
+ let restarted = false;
8993
+ try {
8994
+ restarted = await this.startSessionDecoder(session);
8995
+ } finally {
8996
+ if (!restarted) this.sessions.delete(session.format);
8997
+ }
8998
+ if (restarted) rotated += 1;
8982
8999
  }
8983
9000
  return rotated;
8984
9001
  }
@@ -9238,6 +9255,7 @@ var FrameHandlePlane = class {
9238
9255
  this.sessions.get(format)?.subscriberIds.add(subscriptionId);
9239
9256
  return;
9240
9257
  }
9258
+ if (Date.now() < (this.sessionRetryCooldownUntil.get(format) ?? 0)) return;
9241
9259
  const creation = (async () => {
9242
9260
  const subscriberIds = new Set([subscriptionId]);
9243
9261
  for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
@@ -9248,7 +9266,14 @@ var FrameHandlePlane = class {
9248
9266
  subscriberIds,
9249
9267
  lingerTimer: null
9250
9268
  };
9251
- if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
9269
+ let started = false;
9270
+ try {
9271
+ started = await this.startSessionDecoder(session);
9272
+ } catch (err) {
9273
+ this.sessionRetryCooldownUntil.set(format, Date.now() + this.sessionRetryCooldownMs);
9274
+ throw err;
9275
+ }
9276
+ if (started) this.sessions.set(format, session);
9252
9277
  })();
9253
9278
  this.sessionCreating.set(format, creation);
9254
9279
  try {
@@ -9288,11 +9313,13 @@ var FrameHandlePlane = class {
9288
9313
  tag: `${info.tag}:shm:${format}`
9289
9314
  });
9290
9315
  if (!isDecoderNodeColocated(this.localNodeId, nodeId)) {
9316
+ this.sessionRetryCooldownUntil.set(format, Date.now() + this.sessionRetryCooldownMs);
9291
9317
  this.logger?.warn("frame-handle plane: decoder session landed on a non-local node — destroying (shm ring is node-local)", { meta: {
9292
9318
  format,
9293
9319
  requestedNode: this.localNodeId,
9294
9320
  gotNode: nodeId,
9295
- sessionId
9321
+ sessionId,
9322
+ retryInMs: this.sessionRetryCooldownMs
9296
9323
  } });
9297
9324
  await this.decoderApi.destroySession({ sessionId }).catch((err) => {
9298
9325
  this.logger?.warn("frame-handle plane: failed to destroy mis-placed decoder session", { meta: {
@@ -9303,6 +9330,7 @@ var FrameHandlePlane = class {
9303
9330
  });
9304
9331
  return false;
9305
9332
  }
9333
+ this.sessionRetryCooldownUntil.delete(format);
9306
9334
  const proxy = new DecoderSessionProxy(this.decoderApi, sessionId);
9307
9335
  session.proxy = proxy;
9308
9336
  session.nodeId = nodeId;
@@ -8911,6 +8911,13 @@ var DECODE_FPS_WINDOW_MS = 2e3;
8911
8911
  */
8912
8912
  var SESSION_LINGER_MS = 45e3;
8913
8913
  /**
8914
+ * Default retry cooldown after a decoder session lands on a NON-LOCAL node
8915
+ * (see `FrameHandlePlaneOptions.sessionRetryCooldownMs`). Long enough to
8916
+ * turn a per-packet storm into a slow probe; short enough that the plane
8917
+ * recovers within seconds once the local decoder respawns.
8918
+ */
8919
+ var SESSION_RETRY_COOLDOWN_MS = 1e4;
8920
+ /**
8914
8921
  * Owns the broker's shared-memory frame-handle subscriptions and the
8915
8922
  * per-format `frameSink: 'shm'` decoder sessions that feed them.
8916
8923
  */
@@ -8919,8 +8926,12 @@ var FrameHandlePlane = class {
8919
8926
  logger;
8920
8927
  resolveStreamInfo;
8921
8928
  localNodeId;
8929
+ sessionRetryCooldownMs;
8922
8930
  subscriptions = /* @__PURE__ */ new Map();
8923
8931
  sessions = /* @__PURE__ */ new Map();
8932
+ /** Per-format retry gate set when a session lands on a non-local node:
8933
+ * `ensureSession` skips creation until the epoch-ms deadline passes. */
8934
+ sessionRetryCooldownUntil = /* @__PURE__ */ new Map();
8924
8935
  /** In-flight `ensureSession` creation per format. Coalesces concurrent
8925
8936
  * same-format callers onto ONE decoder session — without it two callers
8926
8937
  * both pass the `sessions.get` check, both `createSession` (two ffmpeg),
@@ -8940,6 +8951,7 @@ var FrameHandlePlane = class {
8940
8951
  this.logger = options.logger;
8941
8952
  this.resolveStreamInfo = options.resolveStreamInfo;
8942
8953
  this.localNodeId = options.localNodeId;
8954
+ this.sessionRetryCooldownMs = options.sessionRetryCooldownMs ?? SESSION_RETRY_COOLDOWN_MS;
8943
8955
  }
8944
8956
  /** Number of active handle subscriptions — surfaced for diagnostics. */
8945
8957
  get subscriberCount() {
@@ -8973,8 +8985,13 @@ var FrameHandlePlane = class {
8973
8985
  nodeId: session.nodeId
8974
8986
  } });
8975
8987
  await this.destroySession(session);
8976
- await this.startSessionDecoder(session);
8977
- rotated += 1;
8988
+ let restarted = false;
8989
+ try {
8990
+ restarted = await this.startSessionDecoder(session);
8991
+ } finally {
8992
+ if (!restarted) this.sessions.delete(session.format);
8993
+ }
8994
+ if (restarted) rotated += 1;
8978
8995
  }
8979
8996
  return rotated;
8980
8997
  }
@@ -9234,6 +9251,7 @@ var FrameHandlePlane = class {
9234
9251
  this.sessions.get(format)?.subscriberIds.add(subscriptionId);
9235
9252
  return;
9236
9253
  }
9254
+ if (Date.now() < (this.sessionRetryCooldownUntil.get(format) ?? 0)) return;
9237
9255
  const creation = (async () => {
9238
9256
  const subscriberIds = new Set([subscriptionId]);
9239
9257
  for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
@@ -9244,7 +9262,14 @@ var FrameHandlePlane = class {
9244
9262
  subscriberIds,
9245
9263
  lingerTimer: null
9246
9264
  };
9247
- if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
9265
+ let started = false;
9266
+ try {
9267
+ started = await this.startSessionDecoder(session);
9268
+ } catch (err) {
9269
+ this.sessionRetryCooldownUntil.set(format, Date.now() + this.sessionRetryCooldownMs);
9270
+ throw err;
9271
+ }
9272
+ if (started) this.sessions.set(format, session);
9248
9273
  })();
9249
9274
  this.sessionCreating.set(format, creation);
9250
9275
  try {
@@ -9284,11 +9309,13 @@ var FrameHandlePlane = class {
9284
9309
  tag: `${info.tag}:shm:${format}`
9285
9310
  });
9286
9311
  if (!isDecoderNodeColocated(this.localNodeId, nodeId)) {
9312
+ this.sessionRetryCooldownUntil.set(format, Date.now() + this.sessionRetryCooldownMs);
9287
9313
  this.logger?.warn("frame-handle plane: decoder session landed on a non-local node — destroying (shm ring is node-local)", { meta: {
9288
9314
  format,
9289
9315
  requestedNode: this.localNodeId,
9290
9316
  gotNode: nodeId,
9291
- sessionId
9317
+ sessionId,
9318
+ retryInMs: this.sessionRetryCooldownMs
9292
9319
  } });
9293
9320
  await this.decoderApi.destroySession({ sessionId }).catch((err) => {
9294
9321
  this.logger?.warn("frame-handle plane: failed to destroy mis-placed decoder session", { meta: {
@@ -9299,6 +9326,7 @@ var FrameHandlePlane = class {
9299
9326
  });
9300
9327
  return false;
9301
9328
  }
9329
+ this.sessionRetryCooldownUntil.delete(format);
9302
9330
  const proxy = new DecoderSessionProxy(this.decoderApi, sessionId);
9303
9331
  session.proxy = proxy;
9304
9332
  session.nodeId = nodeId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.1.23",
3
+ "version": "1.1.24",
4
4
  "description": "CamStack Pipeline bundle — runner, detection, motion, decoders, audio + stream broker. Multi-entry npm package shipping 7 addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",