@rotorsoft/act 1.23.36 → 1.23.37

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/index.js CHANGED
@@ -2543,6 +2543,13 @@ function trace_cycle(logger, leased, fetched, handled, acked, blocked) {
2543
2543
  }
2544
2544
 
2545
2545
  // src/internal/drain-cycle.ts
2546
+ function budget_exhausted(lease, options) {
2547
+ if (!options?.blockOnError || lease.retry <= options.maxRetries)
2548
+ return void 0;
2549
+ const error = `Blocking ${lease.stream} after ${lease.retry} claims with no acknowledged progress \u2014 the lease was lost on every attempt, so the retry budget (${options.maxRetries}) was spent without the handler ever reporting an error. Raise leaseMillis for this handler, then unblock the stream.`;
2550
+ log().error(error);
2551
+ return { lease, handled: 0, acked_at: lease.at, error, block: true };
2552
+ }
2546
2553
  async function run_drain_cycle(ops, registry, batch_handlers, handle, handle_batch, lagging, leading, eventLimit, leaseMillis, on_blocked, lane) {
2547
2554
  const leased = await ops.claim(
2548
2555
  lagging,
@@ -2575,6 +2582,8 @@ async function run_drain_cycle(ops, registry, batch_handlers, handle, handle_bat
2575
2582
  const entry = fetch_map.get(lease.stream);
2576
2583
  const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
2577
2584
  const { payloads } = entry;
2585
+ const exhausted = budget_exhausted(lease, payloads[0]?.options);
2586
+ if (exhausted) return Promise.resolve(exhausted);
2578
2587
  const batchHandler = batch_handlers.get(lease.stream);
2579
2588
  if (batchHandler && payloads.length > 0) {
2580
2589
  return handle_batch({ ...lease, at }, payloads, batchHandler);
@@ -2586,12 +2595,18 @@ async function run_drain_cycle(ops, registry, batch_handlers, handle, handle_bat
2586
2595
  handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
2587
2596
  );
2588
2597
  if (blocked.length) on_blocked(blocked);
2589
- const acked = await ops.ack(
2590
- handled.flatMap((h, i) => {
2591
- const advance = h.handled > 0 ? h.acked_at : leased[i].at;
2592
- return h.defer !== void 0 ? { ...h.lease, at: advance, due: h.defer, retry: -1 } : h.next_attempt_at !== void 0 ? { ...h.lease, at: advance, due: h.next_attempt_at } : h.handled > 0 || !h.error ? { ...h.lease, at: h.acked_at } : [];
2593
- })
2594
- );
2598
+ const submitted = handled.flatMap((h, i) => {
2599
+ const advance = h.handled > 0 ? h.acked_at : leased[i].at;
2600
+ return h.defer !== void 0 ? { ...h.lease, at: advance, due: h.defer, retry: -1 } : h.next_attempt_at !== void 0 ? { ...h.lease, at: advance, due: h.next_attempt_at } : h.handled > 0 || !h.error ? { ...h.lease, at: h.acked_at } : [];
2601
+ });
2602
+ const acked = await ops.ack(submitted);
2603
+ const expected = submitted.filter((l) => l.due === void 0).length;
2604
+ if (acked.length < expected)
2605
+ log().error(
2606
+ new Error(
2607
+ `drain: ${expected - acked.length} of ${expected} acks were dropped \u2014 the lease was taken by another worker mid-handler. That work will be redelivered (at-least-once), but persistent drops mean leaseMillis is too short for this handler.`
2608
+ )
2609
+ );
2595
2610
  const closeable = handled.filter((h) => h.close !== void 0).map((h) => h.close);
2596
2611
  return { leased, fetched, handled, acked, blocked, closeable };
2597
2612
  }