@mark1russell7/service 0.5.9 → 0.5.11

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/build/index.js CHANGED
@@ -431,7 +431,7 @@ const healthRuntime = (description, runs, options = {}) => {
431
431
  if (kind === "liveness") return answer(kind, now.alive);
432
432
  if (kind === "report") return answer(kind, now.ready && now.alive);
433
433
  if (controller === void 0) return answer(kind, now.ready);
434
- const known = now.controllers[controller];
434
+ const known = Object.hasOwn(now.controllers, controller) ? now.controllers[controller] : void 0;
435
435
  if (known === void 0) throw new Error(`${description.name} has no controller ${controller}`);
436
436
  return answer(kind, known, controller);
437
437
  },
@@ -720,6 +720,248 @@ const compilePattern = (pattern) => {
720
720
  };
721
721
  };
722
722
  //#endregion
723
+ //#region src/units.ts
724
+ /** This function gives the cause of an abort as text. */
725
+ const causeOf = (signal) => signal.reason instanceof Error ? signal.reason.message : String(signal.reason ?? "the unit got an abort");
726
+ /**
727
+ * This function does one unit of work, and gives its end. With a request, the unit goes through
728
+ * `life.request`, thus the service counts it, drains it and can cut it. A throw after the abort of
729
+ * the transport is a cut, not a failure (S6 A10). A throw after the bound is a failure.
730
+ */
731
+ const runUnit = async (run) => {
732
+ const { clock, bound, abort, life, request, unit, work } = run;
733
+ const timeout = new AbortController();
734
+ const cancel = Number.isFinite(bound) ? clock.after(bound, () => timeout.abort(/* @__PURE__ */ new Error(`the unit did not end in its unit_processing_time of ${bound} ms`))) : () => {};
735
+ const call = async (tracker) => work(AbortSignal.any(tracker === void 0 ? [abort.signal, timeout.signal] : [
736
+ abort.signal,
737
+ timeout.signal,
738
+ tracker
739
+ ]));
740
+ const thrown = (error) => {
741
+ if (abort.signal.aborted) return {
742
+ kind: "cut",
743
+ cause: causeOf(abort.signal),
744
+ retryable: false
745
+ };
746
+ if (timeout.signal.aborted) return {
747
+ kind: "failed",
748
+ error: timeout.signal.reason
749
+ };
750
+ return {
751
+ kind: "failed",
752
+ error
753
+ };
754
+ };
755
+ try {
756
+ if (request === void 0) try {
757
+ return {
758
+ kind: "done",
759
+ value: await call()
760
+ };
761
+ } catch (error) {
762
+ return thrown(error);
763
+ }
764
+ let collected;
765
+ try {
766
+ const route = typeof request.route === "string" ? request.route : request.route(unit);
767
+ const detail = request.detail?.(unit);
768
+ collected = life.request(request.controller, route, (signal) => call(signal).then((value) => ({ value })), detail === void 0 ? {} : { detail }).collect();
769
+ } catch (error) {
770
+ return {
771
+ kind: "failed",
772
+ error
773
+ };
774
+ }
775
+ const { items, end } = await collected;
776
+ if (end.kind === "done") return {
777
+ kind: "done",
778
+ value: items[0]?.value
779
+ };
780
+ if (end.kind === "failed") return thrown(end.error);
781
+ if (end.kind === "refused") return {
782
+ kind: "refused",
783
+ cause: end.cause
784
+ };
785
+ return {
786
+ kind: "cut",
787
+ cause: end.cause,
788
+ retryable: end.retryable
789
+ };
790
+ } finally {
791
+ cancel();
792
+ }
793
+ };
794
+ /** This function makes a flight set on a clock. */
795
+ const flights = (clock) => {
796
+ const units = /* @__PURE__ */ new Set();
797
+ const idle = /* @__PURE__ */ new Set();
798
+ const lower = /* @__PURE__ */ new Set();
799
+ let aborted = null;
800
+ const settle = () => {
801
+ for (const done of [...lower]) done();
802
+ lower.clear();
803
+ if (units.size === 0) for (const done of [...idle]) done();
804
+ };
805
+ const abort = (reason) => {
806
+ aborted ??= { reason };
807
+ for (const unit of [...units]) if (!unit.signal.aborted) unit.abort(aborted.reason);
808
+ };
809
+ return {
810
+ get size() {
811
+ return units.size;
812
+ },
813
+ add(unit) {
814
+ units.add(unit);
815
+ if (aborted !== null && !unit.signal.aborted) unit.abort(aborted.reason);
816
+ return () => {
817
+ if (units.delete(unit)) settle();
818
+ };
819
+ },
820
+ abort,
821
+ clear() {
822
+ units.clear();
823
+ settle();
824
+ },
825
+ drain(deadline, signal) {
826
+ const cooperative = () => abort(signal.reason);
827
+ if (signal.aborted) cooperative();
828
+ else signal.addEventListener("abort", cooperative, { once: true });
829
+ if (units.size === 0 || deadline <= clock.now()) {
830
+ signal.removeEventListener("abort", cooperative);
831
+ return Promise.resolve();
832
+ }
833
+ return new Promise((resolve) => {
834
+ let cancel = () => {};
835
+ const done = () => {
836
+ idle.delete(done);
837
+ cancel();
838
+ signal.removeEventListener("abort", cooperative);
839
+ resolve();
840
+ };
841
+ idle.add(done);
842
+ cancel = clock.after(Math.max(0, deadline - clock.now()), done);
843
+ });
844
+ },
845
+ less() {
846
+ return new Promise((resolve) => lower.add(resolve));
847
+ },
848
+ release() {
849
+ for (const done of [...lower]) done();
850
+ lower.clear();
851
+ }
852
+ };
853
+ };
854
+ /** This function makes a sleeper on a clock. */
855
+ const sleeper = (clock) => {
856
+ let wakes = 0;
857
+ let stopped = false;
858
+ const sleeps = /* @__PURE__ */ new Set();
859
+ const backoffs = /* @__PURE__ */ new Set();
860
+ const wait = (ms, into) => stopped ? Promise.resolve() : new Promise((resolve) => {
861
+ let cancel = () => {};
862
+ const done = () => {
863
+ if (!into.delete(done)) return;
864
+ cancel();
865
+ resolve();
866
+ };
867
+ into.add(done);
868
+ if (Number.isFinite(ms)) cancel = clock.after(ms, done);
869
+ });
870
+ return {
871
+ get wakes() {
872
+ return wakes;
873
+ },
874
+ sleep(ms, since) {
875
+ if (since !== wakes) return Promise.resolve();
876
+ return wait(ms, sleeps);
877
+ },
878
+ backoff(ms) {
879
+ return wait(ms, backoffs);
880
+ },
881
+ wake() {
882
+ wakes += 1;
883
+ const ended = sleeps.size;
884
+ for (const done of [...sleeps]) done();
885
+ return ended;
886
+ },
887
+ stop() {
888
+ stopped = true;
889
+ for (const done of [...sleeps, ...backoffs]) done();
890
+ }
891
+ };
892
+ };
893
+ /** This function waits for a work until a bound on the clock, in milliseconds. */
894
+ const settleWithin = (clock, ms, work) => new Promise((resolve) => {
895
+ const cancel = Number.isFinite(ms) ? clock.after(ms, () => resolve({ kind: "bound" })) : () => {};
896
+ work.then(() => {
897
+ cancel();
898
+ resolve({ kind: "done" });
899
+ }, (error) => {
900
+ cancel();
901
+ resolve({
902
+ kind: "failed",
903
+ error
904
+ });
905
+ });
906
+ });
907
+ /**
908
+ * This function gives the value of a promise, or `stop` when the signal aborts first. Each wait adds
909
+ * its own listener, and removes it when the promise settles. A race against one pending promise
910
+ * keeps each settled value until that promise ends.
911
+ */
912
+ const untilAbort = (promise, signal, stop) => {
913
+ if (signal.aborted) return Promise.race([promise, Promise.resolve(stop)]);
914
+ return new Promise((resolve, reject) => {
915
+ const onAbort = () => resolve(stop);
916
+ signal.addEventListener("abort", onAbort, { once: true });
917
+ promise.then((value) => {
918
+ signal.removeEventListener("abort", onAbort);
919
+ resolve(value);
920
+ }, (error) => {
921
+ signal.removeEventListener("abort", onAbort);
922
+ reject(error);
923
+ });
924
+ });
925
+ };
926
+ /**
927
+ * This function gives the backoff of the knobs of a loop. The first delay is `retry_base`, and each
928
+ * delay is two times longer, up to `retry_cap`. Each delay has a jitter up to the base, as in the Redis client.
929
+ */
930
+ const retryOf = (knobs, owner) => {
931
+ const base = boundedMillisecondsOf(knobs, "retry_base", owner);
932
+ const backoff = {
933
+ base,
934
+ factor: 2,
935
+ cap: boundedMillisecondsOf(knobs, "retry_cap", owner),
936
+ jitter: {
937
+ kind: "add",
938
+ amount: base
939
+ }
940
+ };
941
+ const problems = backoffProblems(backoff);
942
+ if (problems.length > 0) throw new Error(`${owner}: the retry_base and retry_cap give no backoff: ${problems.join(", ")}`);
943
+ return backoff;
944
+ };
945
+ /**
946
+ * This function gives the problems of the bounds of a loop in a declaration. The close bound fits
947
+ * in the cap of `close transports`. The bound of a unit fits in the cap of `drain`, or the drain can cut a unit (S6 A10).
948
+ */
949
+ const boundProblems = (owner, closeBound, unitBound, description) => {
950
+ const capOf = (name) => {
951
+ const phase = description.shutdown.phases.find((each) => each.name === name);
952
+ return phase === void 0 ? void 0 : valueIn(phase.cap, "ms");
953
+ };
954
+ const found = [];
955
+ const close = capOf("close transports");
956
+ if (close === void 0) found.push(`${owner} needs the phase "close transports" to close`);
957
+ else if (closeBound >= close) found.push(`the close_bound of ${owner}, ${closeBound} ms, does not fit in the cap of "close transports", ${close} ms`);
958
+ const drain = capOf("drain");
959
+ if (drain === void 0) found.push(`${owner} needs the phase "drain" to end its units`);
960
+ else if (!Number.isFinite(unitBound)) found.push(`the unit_processing_time of ${owner} has no bound, thus the drain can cut a unit at its cap of ${drain} ms`);
961
+ else if (unitBound >= drain) found.push(`the unit_processing_time of ${owner}, ${unitBound} ms, does not fit in the cap of "drain", ${drain} ms`);
962
+ return found;
963
+ };
964
+ //#endregion
723
965
  //#region src/requests.ts
724
966
  const isAsyncIterable$1 = (value) => value !== null && (typeof value === "object" || typeof value === "function") && typeof value[Symbol.asyncIterator] === "function";
725
967
  const isPromiseLike = (value) => value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
@@ -815,10 +1057,8 @@ const tracker = (controllers, clock, refuses, choices = /* @__PURE__ */ new Map(
815
1057
  let returned = false;
816
1058
  let source = null;
817
1059
  let unary = null;
818
- let interrupt = () => {};
819
- const interrupted = new Promise((resolve) => {
820
- interrupt = () => resolve("interrupted");
821
- });
1060
+ const interruption = new AbortController();
1061
+ const interrupt = () => interruption.abort();
822
1062
  const cutEnd = (cause) => {
823
1063
  const since = quantity(Math.max(0, clock.now() - started) / 1e3, "s");
824
1064
  const retryable = mayReissue(operation, since);
@@ -887,7 +1127,7 @@ const tracker = (controllers, clock, refuses, choices = /* @__PURE__ */ new Map(
887
1127
  }
888
1128
  const next = async () => {
889
1129
  if (ended) return null;
890
- if (end === null) await Promise.race([ready, interrupted]);
1130
+ if (end === null) await untilAbort(ready, interruption.signal, "interrupted");
891
1131
  if (end === null && unary !== null) {
892
1132
  const { value } = unary;
893
1133
  unary = null;
@@ -905,7 +1145,7 @@ const tracker = (controllers, clock, refuses, choices = /* @__PURE__ */ new Map(
905
1145
  const pending = source.next();
906
1146
  let step;
907
1147
  try {
908
- step = await Promise.race([pending, interrupted]);
1148
+ step = await untilAbort(Promise.resolve(pending), interruption.signal, "interrupted");
909
1149
  } catch (error) {
910
1150
  failed(error);
911
1151
  step = "interrupted";
@@ -1901,10 +2141,16 @@ const marginOf = (inequality, index, bind) => {
1901
2141
  if (typeof bind.margin === "number") return bind.margin;
1902
2142
  return typeof inequality.margin === "number" ? inequality.margin : 0;
1903
2143
  };
2144
+ /** This function tells if an inequality reads a symbol with no limit: "inf", "off" or an infinite number. */
2145
+ const readsAbsent = (inequality, bind) => [...inequality.lhs.terms, ...inequality.rhs.terms].some((term) => {
2146
+ const bound = boundOf$1(bind, keyOf(term));
2147
+ return bound !== void 0 && value(bound) === Infinity;
2148
+ });
1904
2149
  /**
1905
2150
  * This function evaluates each inequality on one binding. "<" holds when lhs + margin < rhs, and
1906
2151
  * ">" holds when lhs > rhs + margin. Only the inequalities with all their symbols bound apply.
1907
- * The others are null.
2152
+ * The others are null. Absent timers can leave no order: the same infinity on each side, or both
2153
+ * signs of infinity on one side. Then the inequality holds only because a timer is absent.
1908
2154
  */
1909
2155
  const evaluate = (rule, bind) => inequalities(rule).map((inequality, index) => {
1910
2156
  const lhs = sideOf(inequality.lhs, bind);
@@ -1916,6 +2162,12 @@ const evaluate = (rule, bind) => inequalities(rule).map((inequality, index) => {
1916
2162
  margin,
1917
2163
  holds: null
1918
2164
  };
2165
+ if ((Number.isNaN(lhs) || Number.isNaN(rhs) || lhs === rhs && !Number.isFinite(lhs)) && readsAbsent(inequality, bind)) return {
2166
+ lhs,
2167
+ rhs,
2168
+ margin,
2169
+ holds: true
2170
+ };
1919
2171
  return {
1920
2172
  lhs,
1921
2173
  rhs,
@@ -1934,13 +2186,7 @@ const verdictOf = (rule, bind, evaluated) => {
1934
2186
  if (applicable.length === 0) return "depends";
1935
2187
  if (applicable.some((index) => evaluated[index]?.holds === false)) return "violated";
1936
2188
  const all = inequalities(rule);
1937
- return applicable.some((index) => {
1938
- const inequality = all[index];
1939
- return [...inequality.lhs.terms, ...inequality.rhs.terms].some((term) => {
1940
- const bound = boundOf$1(bind, keyOf(term));
1941
- return bound !== void 0 && value(bound) === Infinity;
1942
- });
1943
- }) ? "unbounded" : "holds";
2189
+ return applicable.some((index) => readsAbsent(all[index], bind)) ? "unbounded" : "holds";
1944
2190
  };
1945
2191
  //#endregion
1946
2192
  //#region src/lint.ts
@@ -2296,229 +2542,6 @@ const kubernetes = (pod) => {
2296
2542
  };
2297
2543
  };
2298
2544
  //#endregion
2299
- //#region src/units.ts
2300
- /** This function gives the cause of an abort as text. */
2301
- const causeOf = (signal) => signal.reason instanceof Error ? signal.reason.message : String(signal.reason ?? "the unit got an abort");
2302
- /**
2303
- * This function does one unit of work, and gives its end. With a request, the unit goes through
2304
- * `life.request`, thus the service counts it, drains it and can cut it. A throw after the abort of
2305
- * the transport is a cut, not a failure (S6 A10). A throw after the bound is a failure.
2306
- */
2307
- const runUnit = async (run) => {
2308
- const { clock, bound, abort, life, request, unit, work } = run;
2309
- const timeout = new AbortController();
2310
- const cancel = Number.isFinite(bound) ? clock.after(bound, () => timeout.abort(/* @__PURE__ */ new Error(`the unit did not end in its unit_processing_time of ${bound} ms`))) : () => {};
2311
- const call = async (tracker) => work(AbortSignal.any(tracker === void 0 ? [abort.signal, timeout.signal] : [
2312
- abort.signal,
2313
- timeout.signal,
2314
- tracker
2315
- ]));
2316
- const thrown = (error) => {
2317
- if (abort.signal.aborted) return {
2318
- kind: "cut",
2319
- cause: causeOf(abort.signal),
2320
- retryable: false
2321
- };
2322
- if (timeout.signal.aborted) return {
2323
- kind: "failed",
2324
- error: timeout.signal.reason
2325
- };
2326
- return {
2327
- kind: "failed",
2328
- error
2329
- };
2330
- };
2331
- try {
2332
- if (request === void 0) try {
2333
- return {
2334
- kind: "done",
2335
- value: await call()
2336
- };
2337
- } catch (error) {
2338
- return thrown(error);
2339
- }
2340
- let collected;
2341
- try {
2342
- const route = typeof request.route === "string" ? request.route : request.route(unit);
2343
- const detail = request.detail?.(unit);
2344
- collected = life.request(request.controller, route, (signal) => call(signal).then((value) => ({ value })), detail === void 0 ? {} : { detail }).collect();
2345
- } catch (error) {
2346
- return {
2347
- kind: "failed",
2348
- error
2349
- };
2350
- }
2351
- const { items, end } = await collected;
2352
- if (end.kind === "done") return {
2353
- kind: "done",
2354
- value: items[0]?.value
2355
- };
2356
- if (end.kind === "failed") return thrown(end.error);
2357
- if (end.kind === "refused") return {
2358
- kind: "refused",
2359
- cause: end.cause
2360
- };
2361
- return {
2362
- kind: "cut",
2363
- cause: end.cause,
2364
- retryable: end.retryable
2365
- };
2366
- } finally {
2367
- cancel();
2368
- }
2369
- };
2370
- /** This function makes a flight set on a clock. */
2371
- const flights = (clock) => {
2372
- const units = /* @__PURE__ */ new Set();
2373
- const idle = /* @__PURE__ */ new Set();
2374
- const lower = /* @__PURE__ */ new Set();
2375
- let aborted = null;
2376
- const settle = () => {
2377
- for (const done of [...lower]) done();
2378
- lower.clear();
2379
- if (units.size === 0) for (const done of [...idle]) done();
2380
- };
2381
- const abort = (reason) => {
2382
- aborted ??= { reason };
2383
- for (const unit of [...units]) if (!unit.signal.aborted) unit.abort(aborted.reason);
2384
- };
2385
- return {
2386
- get size() {
2387
- return units.size;
2388
- },
2389
- add(unit) {
2390
- units.add(unit);
2391
- if (aborted !== null && !unit.signal.aborted) unit.abort(aborted.reason);
2392
- return () => {
2393
- if (units.delete(unit)) settle();
2394
- };
2395
- },
2396
- abort,
2397
- clear() {
2398
- units.clear();
2399
- settle();
2400
- },
2401
- drain(deadline, signal) {
2402
- const cooperative = () => abort(signal.reason);
2403
- if (signal.aborted) cooperative();
2404
- else signal.addEventListener("abort", cooperative, { once: true });
2405
- if (units.size === 0 || deadline <= clock.now()) {
2406
- signal.removeEventListener("abort", cooperative);
2407
- return Promise.resolve();
2408
- }
2409
- return new Promise((resolve) => {
2410
- let cancel = () => {};
2411
- const done = () => {
2412
- idle.delete(done);
2413
- cancel();
2414
- signal.removeEventListener("abort", cooperative);
2415
- resolve();
2416
- };
2417
- idle.add(done);
2418
- cancel = clock.after(Math.max(0, deadline - clock.now()), done);
2419
- });
2420
- },
2421
- less() {
2422
- return new Promise((resolve) => lower.add(resolve));
2423
- },
2424
- release() {
2425
- for (const done of [...lower]) done();
2426
- lower.clear();
2427
- }
2428
- };
2429
- };
2430
- /** This function makes a sleeper on a clock. */
2431
- const sleeper = (clock) => {
2432
- let wakes = 0;
2433
- let stopped = false;
2434
- const sleeps = /* @__PURE__ */ new Set();
2435
- const backoffs = /* @__PURE__ */ new Set();
2436
- const wait = (ms, into) => stopped ? Promise.resolve() : new Promise((resolve) => {
2437
- let cancel = () => {};
2438
- const done = () => {
2439
- if (!into.delete(done)) return;
2440
- cancel();
2441
- resolve();
2442
- };
2443
- into.add(done);
2444
- if (Number.isFinite(ms)) cancel = clock.after(ms, done);
2445
- });
2446
- return {
2447
- get wakes() {
2448
- return wakes;
2449
- },
2450
- sleep(ms, since) {
2451
- if (since !== wakes) return Promise.resolve();
2452
- return wait(ms, sleeps);
2453
- },
2454
- backoff(ms) {
2455
- return wait(ms, backoffs);
2456
- },
2457
- wake() {
2458
- wakes += 1;
2459
- const ended = sleeps.size;
2460
- for (const done of [...sleeps]) done();
2461
- return ended;
2462
- },
2463
- stop() {
2464
- stopped = true;
2465
- for (const done of [...sleeps, ...backoffs]) done();
2466
- }
2467
- };
2468
- };
2469
- /** This function waits for a work until a bound on the clock, in milliseconds. */
2470
- const settleWithin = (clock, ms, work) => new Promise((resolve) => {
2471
- const cancel = Number.isFinite(ms) ? clock.after(ms, () => resolve({ kind: "bound" })) : () => {};
2472
- work.then(() => {
2473
- cancel();
2474
- resolve({ kind: "done" });
2475
- }, (error) => {
2476
- cancel();
2477
- resolve({
2478
- kind: "failed",
2479
- error
2480
- });
2481
- });
2482
- });
2483
- /**
2484
- * This function gives the backoff of the knobs of a loop. The first delay is `retry_base`, and each
2485
- * delay is two times longer, up to `retry_cap`. Each delay has a jitter up to the base, as in the Redis client.
2486
- */
2487
- const retryOf = (knobs, owner) => {
2488
- const base = boundedMillisecondsOf(knobs, "retry_base", owner);
2489
- const backoff = {
2490
- base,
2491
- factor: 2,
2492
- cap: boundedMillisecondsOf(knobs, "retry_cap", owner),
2493
- jitter: {
2494
- kind: "add",
2495
- amount: base
2496
- }
2497
- };
2498
- const problems = backoffProblems(backoff);
2499
- if (problems.length > 0) throw new Error(`${owner}: the retry_base and retry_cap give no backoff: ${problems.join(", ")}`);
2500
- return backoff;
2501
- };
2502
- /**
2503
- * This function gives the problems of the bounds of a loop in a declaration. The close bound fits
2504
- * in the cap of `close transports`. The bound of a unit fits in the cap of `drain`, or the drain can cut a unit (S6 A10).
2505
- */
2506
- const boundProblems = (owner, closeBound, unitBound, description) => {
2507
- const capOf = (name) => {
2508
- const phase = description.shutdown.phases.find((each) => each.name === name);
2509
- return phase === void 0 ? void 0 : valueIn(phase.cap, "ms");
2510
- };
2511
- const found = [];
2512
- const close = capOf("close transports");
2513
- if (close === void 0) found.push(`${owner} needs the phase "close transports" to close`);
2514
- else if (closeBound >= close) found.push(`the close_bound of ${owner}, ${closeBound} ms, does not fit in the cap of "close transports", ${close} ms`);
2515
- const drain = capOf("drain");
2516
- if (drain === void 0) found.push(`${owner} needs the phase "drain" to end its units`);
2517
- else if (!Number.isFinite(unitBound)) found.push(`the unit_processing_time of ${owner} has no bound, thus the drain can cut a unit at its cap of ${drain} ms`);
2518
- else if (unitBound >= drain) found.push(`the unit_processing_time of ${owner}, ${unitBound} ms, does not fit in the cap of "drain", ${drain} ms`);
2519
- return found;
2520
- };
2521
- //#endregion
2522
2545
  //#region src/worker.ts
2523
2546
  const OWNER$1 = "workerLoop";
2524
2547
  /**
@@ -2706,7 +2729,6 @@ const queueDrain = (knobs, options) => {
2706
2729
  const running = flights(clock);
2707
2730
  const rests = sleeper(clock);
2708
2731
  const intake = new AbortController();
2709
- const stopped = new Promise((resolve) => intake.signal.addEventListener("abort", () => resolve(STOPPED), { once: true }));
2710
2732
  const pending = /* @__PURE__ */ new Set();
2711
2733
  const active = /* @__PURE__ */ new Set();
2712
2734
  let refusals = 0;
@@ -2861,7 +2883,7 @@ const queueDrain = (knobs, options) => {
2861
2883
  kind: "error",
2862
2884
  error
2863
2885
  }));
2864
- const first = await Promise.race([taking, stopped]);
2886
+ const first = await untilAbort(taking, intake.signal, STOPPED);
2865
2887
  if (first === STOPPED || intake.signal.aborted) {
2866
2888
  if (first === STOPPED) taking.then((late) => {
2867
2889
  if (late.kind === "job") handBack(late.job);