adaptive-concurrency 0.3.2 → 0.3.4

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.
Files changed (47) hide show
  1. package/dist/LimitAllotment.d.ts +7 -1
  2. package/dist/LimitAllotment.d.ts.map +1 -1
  3. package/dist/Limiter.d.ts +15 -21
  4. package/dist/Limiter.d.ts.map +1 -1
  5. package/dist/Limiter.js +99 -56
  6. package/dist/RunResult.d.ts +12 -6
  7. package/dist/RunResult.d.ts.map +1 -1
  8. package/dist/RunResult.js +10 -3
  9. package/dist/index.d.ts +3 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -3
  12. package/dist/limiter/acquire-strategies/PartitionedStrategy.d.ts +34 -5
  13. package/dist/limiter/acquire-strategies/PartitionedStrategy.d.ts.map +1 -1
  14. package/dist/limiter/acquire-strategies/PartitionedStrategy.js +34 -9
  15. package/dist/limiter/acquire-strategies/SemaphoreStrategy.d.ts +13 -0
  16. package/dist/limiter/acquire-strategies/SemaphoreStrategy.d.ts.map +1 -0
  17. package/dist/limiter/acquire-strategies/SemaphoreStrategy.js +23 -0
  18. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.d.ts +36 -0
  19. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.d.ts.map +1 -0
  20. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.js +117 -0
  21. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts +2 -1
  22. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts.map +1 -1
  23. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js +10 -2
  24. package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.d.ts +19 -15
  25. package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.d.ts.map +1 -1
  26. package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.js +13 -61
  27. package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.d.ts +4 -8
  28. package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.d.ts.map +1 -1
  29. package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.js +13 -49
  30. package/dist/limiter/factories/makeBlockingLimiter.d.ts +3 -1
  31. package/dist/limiter/factories/makeBlockingLimiter.d.ts.map +1 -1
  32. package/dist/limiter/factories/makeBlockingLimiter.js +3 -2
  33. package/dist/limiter/factories/makeLifoBlockingLimiter.d.ts +1 -1
  34. package/dist/limiter/factories/makeLifoBlockingLimiter.d.ts.map +1 -1
  35. package/dist/limiter/factories/makeLifoBlockingLimiter.js +1 -1
  36. package/dist/limiter/factories/makePartitionedBlockingLimiter.d.ts +3 -1
  37. package/dist/limiter/factories/makePartitionedBlockingLimiter.d.ts.map +1 -1
  38. package/dist/limiter/factories/makePartitionedBlockingLimiter.js +5 -4
  39. package/dist/limiter/factories/makeSimpleLimiter.d.ts.map +1 -1
  40. package/dist/limiter/factories/makeSimpleLimiter.js +2 -1
  41. package/dist/utils/LinkedWaiterQueue.d.ts +21 -0
  42. package/dist/utils/LinkedWaiterQueue.d.ts.map +1 -0
  43. package/dist/utils/LinkedWaiterQueue.js +81 -0
  44. package/dist/utils/index.d.ts +1 -0
  45. package/dist/utils/index.d.ts.map +1 -1
  46. package/dist/utils/index.js +1 -0
  47. package/package.json +4 -3
@@ -9,9 +9,12 @@ const PARTITION_TAG_NAME = "partition";
9
9
  * max(1, ceil(globalLimit * percent))`.
10
10
  *
11
11
  * Admission policy:
12
- * - If `globalInflight < globalLimit`, the request is admitted regardless of
13
- * its partition's current limitAtGlobalSaturation usage (bursting while slack
14
- * exists).
12
+ * - If `globalInflight < globalLimit`, admission is controlled by partition
13
+ * burst policy:
14
+ * - `unbounded` (default): always admit while global slack exists.
15
+ * - `capped`: admit up to
16
+ * `ceil(limitAtGlobalSaturation * maxBurstMultiplier)`.
17
+ * - `none`: admit only up to `limitAtGlobalSaturation`.
15
18
  *
16
19
  * - If `globalInflight >= globalLimit`, admission is checked against the
17
20
  * resolved partition's limitAtGlobalSaturation only (`tryAcquire` on that
@@ -23,8 +26,7 @@ const PARTITION_TAG_NAME = "partition";
23
26
  *
24
27
  * Examples (global limit = 10, A=50%, B=50% => limitsAtGlobalSaturation: A=5,
25
28
  * B=5):
26
- * - **Bursting:** A can reach inflight 10 while B is 0 (below-saturation
27
- * admissions do not enforce per-partition limits at global saturation).
29
+ * - **Unbounded bursting:** A can reach inflight 10 while B is 0.
28
30
  *
29
31
  * - **Limit-at-global-saturation catch-up at saturation:** if A=10 and B=0,
30
32
  * then B requests can still be admitted up to B=5 via `tryAcquire`, so global
@@ -65,6 +67,7 @@ export class PartitionedStrategy {
65
67
  new Partition({
66
68
  name,
67
69
  percent: cfg.percent,
70
+ burstMode: cfg.burstMode ?? { kind: "unbounded" },
68
71
  initialLimitAtGlobalSaturation: partitionLimitAtGlobalSaturationForGlobal(initialLimit, cfg.percent),
69
72
  registry,
70
73
  }),
@@ -72,9 +75,9 @@ export class PartitionedStrategy {
72
75
  this.unknownPartition = new Partition({
73
76
  name: "unknown",
74
77
  percent: 0,
78
+ burstMode: { kind: "unbounded" },
75
79
  // Java never calls updateLimit on unknown; limitAtGlobalSaturation stays
76
- // 0 so
77
- // tryAcquire always fails at global cap.
80
+ // 0, so tryAcquire always fails at global saturation.
78
81
  initialLimitAtGlobalSaturation: 0,
79
82
  registry,
80
83
  });
@@ -84,8 +87,7 @@ export class PartitionedStrategy {
84
87
  if (state.inflight >= state.limit) {
85
88
  return partition.tryAcquire();
86
89
  }
87
- partition.acquire();
88
- return true;
90
+ return partition.acquireWithinBurst();
89
91
  }
90
92
  onAllotmentReleased(context) {
91
93
  this.resolvePartition(context).release();
@@ -107,6 +109,7 @@ export class PartitionedStrategy {
107
109
  return {
108
110
  name: partition.name,
109
111
  percent: partition.percent,
112
+ burstMode: partition.burstMode,
110
113
  limitAtGlobalSaturation: partition.limitAtGlobalSaturation,
111
114
  inFlight: partition.inFlight,
112
115
  isLimitAtGlobalSaturationExceeded: partition.isLimitAtGlobalSaturationExceeded,
@@ -129,6 +132,7 @@ function partitionLimitAtGlobalSaturationForGlobal(totalGlobalLimit, percent) {
129
132
  class Partition {
130
133
  name;
131
134
  percent;
135
+ burstMode;
132
136
  inflightDistribution;
133
137
  limitAtGlobalSaturationGauge;
134
138
  _inflight = 0;
@@ -139,7 +143,12 @@ class Partition {
139
143
  }
140
144
  this.name = init.name;
141
145
  this.percent = init.percent;
146
+ this.burstMode = init.burstMode;
142
147
  this._limitAtGlobalSaturation = init.initialLimitAtGlobalSaturation;
148
+ if (this.burstMode.kind === "capped" &&
149
+ this.burstMode.maxBurstMultiplier < 1.0) {
150
+ throw new Error("maxBurstMultiplier must be >= 1.0");
151
+ }
143
152
  const registry = init.registry;
144
153
  this.inflightDistribution = registry.distribution(MetricIds.INFLIGHT_NAME, PARTITION_TAG_NAME, this.name);
145
154
  this.limitAtGlobalSaturationGauge = registry.gauge(MetricIds.PARTITION_LIMIT_NAME, () => this._limitAtGlobalSaturation, PARTITION_TAG_NAME, this.name);
@@ -163,6 +172,13 @@ class Partition {
163
172
  this._inflight++;
164
173
  this.inflightDistribution.addSample(this._inflight);
165
174
  }
175
+ acquireWithinBurst() {
176
+ if (this._inflight >= this.burstCapBelowGlobalSaturation()) {
177
+ return false;
178
+ }
179
+ this.acquire();
180
+ return true;
181
+ }
166
182
  tryAcquire() {
167
183
  if (this._inflight < this._limitAtGlobalSaturation) {
168
184
  this._inflight++;
@@ -177,6 +193,15 @@ class Partition {
177
193
  get inFlight() {
178
194
  return this._inflight;
179
195
  }
196
+ burstCapBelowGlobalSaturation() {
197
+ if (this.burstMode.kind === "unbounded") {
198
+ return Number.POSITIVE_INFINITY;
199
+ }
200
+ if (this.burstMode.kind === "none") {
201
+ return this._limitAtGlobalSaturation;
202
+ }
203
+ return Math.max(this._limitAtGlobalSaturation, Math.ceil(this._limitAtGlobalSaturation * this.burstMode.maxBurstMultiplier));
204
+ }
180
205
  toString() {
181
206
  return `Partition [pct=${this.percent}, limitAtGlobalSaturation=${this._limitAtGlobalSaturation}, inflight=${this._inflight}]`;
182
207
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Simple semaphore-based acquire strategy. Tracks a permits counter that is
3
+ * decremented when an allotment is taken and incremented on release. When
4
+ * permits reach zero, `tryAcquireAllotment` returns false.
5
+ */
6
+ export declare class SemaphoreStrategy {
7
+ private permits;
8
+ constructor(initialLimit: number);
9
+ tryAcquireAllotment(): boolean;
10
+ onAllotmentReleased(): void;
11
+ onLimitChanged(oldLimit: number, newLimit: number): void;
12
+ }
13
+ //# sourceMappingURL=SemaphoreStrategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SemaphoreStrategy.d.ts","sourceRoot":"","sources":["../../../src/limiter/acquire-strategies/SemaphoreStrategy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAS;gBAEZ,YAAY,EAAE,MAAM;IAIhC,mBAAmB,IAAI,OAAO;IAM9B,mBAAmB,IAAI,IAAI;IAI3B,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGzD"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Simple semaphore-based acquire strategy. Tracks a permits counter that is
3
+ * decremented when an allotment is taken and incremented on release. When
4
+ * permits reach zero, `tryAcquireAllotment` returns false.
5
+ */
6
+ export class SemaphoreStrategy {
7
+ permits;
8
+ constructor(initialLimit) {
9
+ this.permits = initialLimit;
10
+ }
11
+ tryAcquireAllotment() {
12
+ if (this.permits <= 0)
13
+ return false;
14
+ this.permits--;
15
+ return true;
16
+ }
17
+ onAllotmentReleased() {
18
+ this.permits++;
19
+ }
20
+ onLimitChanged(oldLimit, newLimit) {
21
+ this.permits += newLimit - oldLimit;
22
+ }
23
+ }
@@ -0,0 +1,36 @@
1
+ import type { LimitAllotment } from "../../LimitAllotment.js";
2
+ import type { AcquireResult, AllotmentUnavailableStrategy } from "../../Limiter.js";
3
+ import type { WaiterHandle } from "../../utils/LinkedWaiterQueue.js";
4
+ type Waiter<ContextT> = {
5
+ context: ContextT;
6
+ retry: (context: ContextT) => AcquireResult;
7
+ handle: WaiterHandle;
8
+ resolve: (allotment: LimitAllotment | undefined) => void;
9
+ };
10
+ export declare const MAX_TIMEOUT: number;
11
+ export type BlockingBacklogRejectionOptions<ContextT> = {
12
+ backlogSize: number;
13
+ backlogTimeout: number | ((context: ContextT) => number);
14
+ queue: WaiterQueue<Waiter<ContextT>>;
15
+ };
16
+ export type WaiterQueue<WaiterT extends Waiter<any>> = {
17
+ enqueue: (waiter: Omit<WaiterT, "handle">) => WaiterT;
18
+ peekHead: () => WaiterT | undefined;
19
+ removeByHandle: (handle: WaiterHandle) => boolean;
20
+ size: () => number;
21
+ };
22
+ export declare class BlockingBacklogRejection<ContextT> implements AllotmentUnavailableStrategy<ContextT> {
23
+ private readonly backlogSize;
24
+ private readonly getBacklogTimeout;
25
+ private readonly queue;
26
+ private drainInProgress;
27
+ private releaseDuringDrain;
28
+ constructor(options: BlockingBacklogRejectionOptions<ContextT>);
29
+ onAllotmentUnavailable(context: ContextT, retry: (context: ContextT) => AcquireResult, signal?: AbortSignal): Promise<LimitAllotment | undefined>;
30
+ onAllotmentReleased(): Promise<void>;
31
+ onLimitChanged(oldLimit: number, newLimit: number): void;
32
+ private waitInBacklog;
33
+ private assertTimeoutWithinBounds;
34
+ }
35
+ export {};
36
+ //# sourceMappingURL=BlockingBacklogRejection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockingBacklogRejection.d.ts","sourceRoot":"","sources":["../../../src/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EACV,aAAa,EACb,4BAA4B,EAC7B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAErE,KAAK,MAAM,CAAC,QAAQ,IAAI;IACtB,OAAO,EAAE,QAAQ,CAAC;IAClB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,CAAC;IAC5C,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,CAAC,SAAS,EAAE,cAAc,GAAG,SAAS,KAAK,IAAI,CAAC;CAC1D,CAAC;AAEF,eAAO,MAAM,WAAW,QAAiB,CAAC;AAE1C,MAAM,MAAM,+BAA+B,CAAC,QAAQ,IAAI;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC,CAAC;IACzD,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,GAAG,CAAC,IAAI;IACrD,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,OAAO,CAAC;IACtD,QAAQ,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IACpC,cAAc,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC;IAClD,IAAI,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,wBAAwB,CACnC,QAAQ,CACR,YAAW,4BAA4B,CAAC,QAAQ,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAgC;IAClE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,kBAAkB,CAAS;gBAEvB,OAAO,EAAE,+BAA+B,CAAC,QAAQ,CAAC;IA6B9D,sBAAsB,CACpB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAYhC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4C1C,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQxD,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,yBAAyB;CAUlC"}
@@ -0,0 +1,117 @@
1
+ export const MAX_TIMEOUT = 60 * 60 * 1000; // 1 hour
2
+ export class BlockingBacklogRejection {
3
+ backlogSize;
4
+ getBacklogTimeout;
5
+ queue;
6
+ drainInProgress = false;
7
+ releaseDuringDrain = false;
8
+ constructor(options) {
9
+ const backlogSize = options.backlogSize;
10
+ if (backlogSize !== Number.POSITIVE_INFINITY &&
11
+ (!Number.isFinite(backlogSize) || backlogSize < 0)) {
12
+ throw new RangeError("BlockingBacklogRejection: backlogSize must be a finite number greater than or equal to 0, or Infinity");
13
+ }
14
+ this.backlogSize = backlogSize;
15
+ this.queue = options.queue;
16
+ const backlogTimeout = options.backlogTimeout;
17
+ if (typeof backlogTimeout === "number") {
18
+ this.assertTimeoutWithinBounds(backlogTimeout);
19
+ this.getBacklogTimeout = () => backlogTimeout;
20
+ return;
21
+ }
22
+ this.getBacklogTimeout = (context) => {
23
+ const contextTimeout = backlogTimeout(context);
24
+ this.assertTimeoutWithinBounds(contextTimeout);
25
+ return contextTimeout;
26
+ };
27
+ }
28
+ onAllotmentUnavailable(context, retry, signal) {
29
+ if (signal?.aborted) {
30
+ return Promise.resolve(undefined);
31
+ }
32
+ if (this.queue.size() >= this.backlogSize) {
33
+ return Promise.resolve(undefined);
34
+ }
35
+ return this.waitInBacklog(context, retry, signal);
36
+ }
37
+ async onAllotmentReleased() {
38
+ if (this.drainInProgress) {
39
+ this.releaseDuringDrain = true;
40
+ return;
41
+ }
42
+ if (this.queue.size() === 0) {
43
+ return;
44
+ }
45
+ this.drainInProgress = true;
46
+ try {
47
+ while (this.queue.size() > 0) {
48
+ const waiter = this.queue.peekHead();
49
+ if (!waiter) {
50
+ return;
51
+ }
52
+ this.releaseDuringDrain = false;
53
+ const allotment = await waiter.retry(waiter.context);
54
+ if (!allotment) {
55
+ if (this.releaseDuringDrain) {
56
+ // A slot was released while retry was in-flight but the
57
+ // notification was suppressed by drainInProgress. Retry the
58
+ // loop so the freed capacity isn't lost.
59
+ continue;
60
+ }
61
+ return;
62
+ }
63
+ if (!this.queue.removeByHandle(waiter.handle)) {
64
+ // Waiter expired while retry was in-flight. Release the acquired slot
65
+ // so future retries can serve active queued waiters.
66
+ await allotment.releaseAndIgnore();
67
+ continue;
68
+ }
69
+ waiter.resolve(allotment);
70
+ }
71
+ }
72
+ finally {
73
+ this.drainInProgress = false;
74
+ }
75
+ }
76
+ onLimitChanged(oldLimit, newLimit) {
77
+ if (newLimit > oldLimit) {
78
+ queueMicrotask(() => {
79
+ void this.onAllotmentReleased();
80
+ });
81
+ }
82
+ }
83
+ waitInBacklog(context, retry, signal) {
84
+ const timeout = this.getBacklogTimeout(context);
85
+ return new Promise((resolve) => {
86
+ let settled = false;
87
+ const settle = (allotment) => {
88
+ if (settled)
89
+ return;
90
+ settled = true;
91
+ cleanup();
92
+ resolve(allotment);
93
+ };
94
+ const waiter = this.queue.enqueue({
95
+ context,
96
+ retry,
97
+ resolve: (allotment) => settle(allotment),
98
+ });
99
+ const timer = setTimeout(() => settle(undefined), timeout);
100
+ const onAbort = () => settle(undefined);
101
+ const cleanup = () => {
102
+ clearTimeout(timer);
103
+ signal?.removeEventListener("abort", onAbort);
104
+ this.queue.removeByHandle(waiter.handle);
105
+ };
106
+ signal?.addEventListener("abort", onAbort, { once: true });
107
+ });
108
+ }
109
+ assertTimeoutWithinBounds(timeout) {
110
+ if (!Number.isFinite(timeout) || timeout < 0) {
111
+ throw new RangeError("Timeout must be a finite number greater than or equal to 0");
112
+ }
113
+ if (timeout > MAX_TIMEOUT) {
114
+ throw new RangeError(`Timeout cannot be greater than ${MAX_TIMEOUT} ms`);
115
+ }
116
+ }
117
+ }
@@ -1,5 +1,5 @@
1
1
  import type { LimitAllotment } from "../../LimitAllotment.js";
2
- import type { AllotmentUnavailableStrategy, AcquireResult } from "../../Limiter.js";
2
+ import type { AcquireResult, AllotmentUnavailableStrategy } from "../../Limiter.js";
3
3
  import type { DelayedRejectStrategy } from "./DelayedRejectStrategy.js";
4
4
  /**
5
5
  * Composes "delay then reject" backoff with blocking behavior. On rejection:
@@ -15,5 +15,6 @@ export declare class DelayedThenBlockingRejection<ContextT> implements Allotment
15
15
  });
16
16
  onAllotmentUnavailable(context: ContextT, retry: (context: ContextT) => AcquireResult, signal?: AbortSignal): Promise<LimitAllotment | undefined>;
17
17
  onAllotmentReleased(): Promise<void>;
18
+ onLimitChanged(oldLimit: number, newLimit: number): void;
18
19
  }
19
20
  //# sourceMappingURL=DelayedThenBlockingRejection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DelayedThenBlockingRejection.d.ts","sourceRoot":"","sources":["../../../src/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EACV,4BAA4B,EAC5B,aAAa,EACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE;;;;GAIG;AACH,qBAAa,4BAA4B,CACvC,QAAQ,CACR,YAAW,4BAA4B,CAAC,QAAQ,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAChE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyC;gBAE9D,OAAO,EAAE;QACnB,aAAa,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC/C,gBAAgB,EAAE,4BAA4B,CAAC,QAAQ,CAAC,CAAC;KAC1D;IAKK,sBAAsB,CAC1B,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAYhC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;CAI3C"}
1
+ {"version":3,"file":"DelayedThenBlockingRejection.d.ts","sourceRoot":"","sources":["../../../src/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EACV,aAAa,EACb,4BAA4B,EAC7B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE;;;;GAIG;AACH,qBAAa,4BAA4B,CACvC,QAAQ,CACR,YAAW,4BAA4B,CAAC,QAAQ,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAChE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyC;gBAE9D,OAAO,EAAE;QACnB,aAAa,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC/C,gBAAgB,EAAE,4BAA4B,CAAC,QAAQ,CAAC,CAAC;KAC1D;IAKK,sBAAsB,CAC1B,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAkBhC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAK1C,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGzD"}
@@ -16,11 +16,19 @@ export class DelayedThenBlockingRejection {
16
16
  return undefined;
17
17
  }
18
18
  const allotment = await retry(context);
19
- return (allotment ??
20
- this.blockingStrategy.onAllotmentUnavailable(context, retry, signal));
19
+ if (allotment) {
20
+ return allotment;
21
+ }
22
+ if (signal?.aborted) {
23
+ return undefined;
24
+ }
25
+ return this.blockingStrategy.onAllotmentUnavailable(context, retry, signal);
21
26
  }
22
27
  async onAllotmentReleased() {
23
28
  await this.delayStrategy.onAllotmentReleased();
24
29
  await this.blockingStrategy.onAllotmentReleased();
25
30
  }
31
+ onLimitChanged(oldLimit, newLimit) {
32
+ this.blockingStrategy.onLimitChanged?.(oldLimit, newLimit);
33
+ }
26
34
  }
@@ -1,5 +1,17 @@
1
- import type { LimitAllotment } from "../../LimitAllotment.js";
2
- import type { AllotmentUnavailableStrategy, AcquireResult } from "../../Limiter.js";
1
+ import type { AcquireResult, AllotmentUnavailableStrategy } from "../../Limiter.js";
2
+ export type FifoBlockingRejectionOptions<ContextT> = {
3
+ /**
4
+ * Maximum number of blocked callers in the backlog.
5
+ * Default: unbounded
6
+ */
7
+ backlogSize?: number | undefined;
8
+ /**
9
+ * Maximum timeout for callers blocked on the limiter, in milliseconds.
10
+ * Can be a fixed number or a function that derives the timeout from the
11
+ * request context (e.g. from a deadline). Default: 3_600_000 (1 hour).
12
+ */
13
+ backlogTimeout?: number | ((context: ContextT) => number) | undefined;
14
+ };
3
15
  /**
4
16
  * Rejection strategy that blocks the caller in a FIFO queue when the limit
5
17
  * has been reached, waiting for a slot to open up. This strategy favors
@@ -9,18 +21,10 @@ import type { AllotmentUnavailableStrategy, AcquireResult } from "../../Limiter.
9
21
  * that resolves when a token becomes available.
10
22
  */
11
23
  export declare class FifoBlockingRejection<ContextT> implements AllotmentUnavailableStrategy<ContextT> {
12
- private readonly timeout;
13
- private readonly waiters;
14
- constructor(options?: {
15
- /**
16
- * Maximum time in milliseconds to wait for a slot to become available.
17
- * Default: 3_600_000 (1 hour).
18
- */
19
- timeout?: number | undefined;
20
- });
21
- onAllotmentUnavailable(_context: ContextT, retry: (context: ContextT) => AcquireResult, signal?: AbortSignal): Promise<LimitAllotment | undefined>;
22
- onAllotmentReleased(): void;
23
- private acquireAsync;
24
- private waitForRelease;
24
+ private readonly delegate;
25
+ constructor(options?: FifoBlockingRejectionOptions<ContextT>);
26
+ onAllotmentUnavailable(context: ContextT, retry: (context: ContextT) => AcquireResult, signal?: AbortSignal): Promise<import("../../LimitAllotment.js").LimitAllotment | undefined>;
27
+ onAllotmentReleased(): Promise<void>;
28
+ onLimitChanged(oldLimit: number, newLimit: number): void;
25
29
  }
26
30
  //# sourceMappingURL=FifoBlockingRejection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FifoBlockingRejection.d.ts","sourceRoot":"","sources":["../../../src/limiter/allocation-unavailable-strategies/FifoBlockingRejection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EACV,4BAA4B,EAC5B,aAAa,EACd,MAAM,kBAAkB,CAAC;AAM1B;;;;;;;GAOG;AACH,qBAAa,qBAAqB,CAChC,QAAQ,CACR,YAAW,4BAA4B,CAAC,QAAQ,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAG3C,OAAO,GAAE;QACP;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KACzB;IASR,sBAAsB,CACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAItC,mBAAmB,IAAI,IAAI;YAOb,YAAY;IA6B1B,OAAO,CAAC,cAAc;CAoCvB"}
1
+ {"version":3,"file":"FifoBlockingRejection.d.ts","sourceRoot":"","sources":["../../../src/limiter/allocation-unavailable-strategies/FifoBlockingRejection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,4BAA4B,EAC7B,MAAM,kBAAkB,CAAC;AAO1B,MAAM,MAAM,4BAA4B,CAAC,QAAQ,IAAI;IACnD;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;CACvE,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,qBAAqB,CAChC,QAAQ,CACR,YAAW,4BAA4B,CAAC,QAAQ,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;gBAElD,OAAO,GAAE,4BAA4B,CAAC,QAAQ,CAAM;IAQhE,sBAAsB,CACpB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW;IAKtB,mBAAmB;IAInB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGzD"}
@@ -1,4 +1,5 @@
1
- const MAX_TIMEOUT = 60 * 60 * 1000; // 1 hour
1
+ import { LinkedWaiterQueue } from "../../utils/LinkedWaiterQueue.js";
2
+ import { BlockingBacklogRejection, MAX_TIMEOUT, } from "./BlockingBacklogRejection.js";
2
3
  /**
3
4
  * Rejection strategy that blocks the caller in a FIFO queue when the limit
4
5
  * has been reached, waiting for a slot to open up. This strategy favors
@@ -8,70 +9,21 @@ const MAX_TIMEOUT = 60 * 60 * 1000; // 1 hour
8
9
  * that resolves when a token becomes available.
9
10
  */
10
11
  export class FifoBlockingRejection {
11
- timeout;
12
- waiters = [];
12
+ delegate;
13
13
  constructor(options = {}) {
14
- const t = options.timeout ?? MAX_TIMEOUT;
15
- if (t > MAX_TIMEOUT) {
16
- throw new Error(`Timeout cannot be greater than ${MAX_TIMEOUT} ms`);
17
- }
18
- this.timeout = t;
14
+ this.delegate = new BlockingBacklogRejection({
15
+ backlogSize: options.backlogSize ?? Number.POSITIVE_INFINITY,
16
+ backlogTimeout: options.backlogTimeout ?? MAX_TIMEOUT,
17
+ queue: new LinkedWaiterQueue("back"),
18
+ });
19
19
  }
20
- onAllotmentUnavailable(_context, retry, signal) {
21
- return this.acquireAsync(_context, retry, signal);
20
+ onAllotmentUnavailable(context, retry, signal) {
21
+ return this.delegate.onAllotmentUnavailable(context, retry, signal);
22
22
  }
23
23
  onAllotmentReleased() {
24
- const waiters = this.waiters.splice(0);
25
- for (const waiter of waiters) {
26
- waiter();
27
- }
28
- }
29
- async acquireAsync(context, retry, signal) {
30
- const deadline = performance.now() + this.timeout;
31
- while (true) {
32
- const remaining = deadline - performance.now();
33
- if (remaining <= 0) {
34
- return undefined;
35
- }
36
- if (signal?.aborted) {
37
- return undefined;
38
- }
39
- const allotment = await retry(context);
40
- if (allotment) {
41
- return allotment;
42
- }
43
- const acquired = await this.waitForRelease(remaining, signal);
44
- if (!acquired) {
45
- return undefined;
46
- }
47
- }
24
+ return this.delegate.onAllotmentReleased();
48
25
  }
49
- waitForRelease(timeoutMs, signal) {
50
- if (signal?.aborted) {
51
- return Promise.resolve(false);
52
- }
53
- return new Promise((resolve) => {
54
- let settled = false;
55
- const settle = (acquired) => {
56
- if (settled)
57
- return;
58
- settled = true;
59
- cleanup();
60
- resolve(acquired);
61
- };
62
- const waiter = () => settle(true);
63
- this.waiters.push(waiter);
64
- const timer = setTimeout(() => settle(false), timeoutMs);
65
- const onAbort = () => settle(false);
66
- const cleanup = () => {
67
- clearTimeout(timer);
68
- signal?.removeEventListener("abort", onAbort);
69
- const idx = this.waiters.indexOf(waiter);
70
- if (idx !== -1) {
71
- this.waiters.splice(idx, 1);
72
- }
73
- };
74
- signal?.addEventListener("abort", onAbort, { once: true });
75
- });
26
+ onLimitChanged(oldLimit, newLimit) {
27
+ this.delegate.onLimitChanged(oldLimit, newLimit);
76
28
  }
77
29
  }
@@ -1,5 +1,4 @@
1
- import type { LimitAllotment } from "../../LimitAllotment.js";
2
- import type { AllotmentUnavailableStrategy, AcquireResult } from "../../Limiter.js";
1
+ import type { AcquireResult, AllotmentUnavailableStrategy } from "../../Limiter.js";
3
2
  export interface LifoBlockingRejectionOptions<ContextT> {
4
3
  /**
5
4
  * Maximum number of blocked callers in the backlog. Default: 100
@@ -19,13 +18,10 @@ export interface LifoBlockingRejectionOptions<ContextT> {
19
18
  * latencies low and minimizing timeouts.
20
19
  */
21
20
  export declare class LifoBlockingRejection<ContextT> implements AllotmentUnavailableStrategy<ContextT> {
22
- private readonly backlogSize;
23
- private readonly getBacklogTimeout;
24
- private readonly backlog;
25
- private retry;
21
+ private readonly delegate;
26
22
  constructor(options?: LifoBlockingRejectionOptions<ContextT>);
27
- onAllotmentUnavailable(context: ContextT, retry: (context: ContextT) => AcquireResult, signal?: AbortSignal): Promise<LimitAllotment | undefined>;
23
+ onAllotmentUnavailable(context: ContextT, retry: (context: ContextT) => AcquireResult, signal?: AbortSignal): Promise<import("../../LimitAllotment.js").LimitAllotment | undefined>;
28
24
  onAllotmentReleased(): Promise<void>;
29
- private waitForBacklog;
25
+ onLimitChanged(oldLimit: number, newLimit: number): void;
30
26
  }
31
27
  //# sourceMappingURL=LifoBlockingRejection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LifoBlockingRejection.d.ts","sourceRoot":"","sources":["../../../src/limiter/allocation-unavailable-strategies/LifoBlockingRejection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EACV,4BAA4B,EAC5B,aAAa,EACd,MAAM,kBAAkB,CAAC;AAO1B,MAAM,WAAW,4BAA4B,CAAC,QAAQ;IACpD;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;CACvE;AAED;;;;;GAKG;AACH,qBAAa,qBAAqB,CAChC,QAAQ,CACR,YAAW,4BAA4B,CAAC,QAAQ,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAgC;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IAEvD,OAAO,CAAC,KAAK,CAAqD;gBAEtD,OAAO,GAAE,4BAA4B,CAAC,QAAQ,CAAM;IAQhE,sBAAsB,CACpB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAUhC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAW1C,OAAO,CAAC,cAAc;CAsCvB"}
1
+ {"version":3,"file":"LifoBlockingRejection.d.ts","sourceRoot":"","sources":["../../../src/limiter/allocation-unavailable-strategies/LifoBlockingRejection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,4BAA4B,EAC7B,MAAM,kBAAkB,CAAC;AAM1B,MAAM,WAAW,4BAA4B,CAAC,QAAQ;IACpD;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;CACvE;AAED;;;;;GAKG;AACH,qBAAa,qBAAqB,CAChC,QAAQ,CACR,YAAW,4BAA4B,CAAC,QAAQ,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;gBAElD,OAAO,GAAE,4BAA4B,CAAC,QAAQ,CAAM;IAQhE,sBAAsB,CACpB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,EAC3C,MAAM,CAAC,EAAE,WAAW;IAKtB,mBAAmB;IAInB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;CAGzD"}
@@ -1,3 +1,5 @@
1
+ import { LinkedWaiterQueue } from "../../utils/LinkedWaiterQueue.js";
2
+ import { BlockingBacklogRejection, } from "./BlockingBacklogRejection.js";
1
3
  /**
2
4
  * Rejection strategy that blocks the caller in a LIFO queue when the limit
3
5
  * has been reached. This strategy favors availability over latency by
@@ -5,59 +7,21 @@
5
7
  * latencies low and minimizing timeouts.
6
8
  */
7
9
  export class LifoBlockingRejection {
8
- backlogSize;
9
- getBacklogTimeout;
10
- backlog = [];
11
- retry;
10
+ delegate;
12
11
  constructor(options = {}) {
13
- this.backlogSize = options.backlogSize ?? 100;
14
- const timeout = options.backlogTimeout ?? 1_000;
15
- this.getBacklogTimeout =
16
- typeof timeout === "number" ? () => timeout : timeout;
12
+ this.delegate = new BlockingBacklogRejection({
13
+ backlogSize: options.backlogSize ?? 100,
14
+ backlogTimeout: options.backlogTimeout ?? 1_000,
15
+ queue: new LinkedWaiterQueue("front"),
16
+ });
17
17
  }
18
18
  onAllotmentUnavailable(context, retry, signal) {
19
- this.retry = retry;
20
- if (this.backlog.length >= this.backlogSize) {
21
- return Promise.resolve(undefined);
22
- }
23
- return this.waitForBacklog(context, signal);
19
+ return this.delegate.onAllotmentUnavailable(context, retry, signal);
24
20
  }
25
- async onAllotmentReleased() {
26
- if (this.backlog.length === 0 || !this.retry)
27
- return;
28
- const waiter = this.backlog[0];
29
- const allotment = await this.retry(waiter.context);
30
- if (allotment) {
31
- this.backlog.shift();
32
- waiter.resolve(allotment);
33
- }
21
+ onAllotmentReleased() {
22
+ return this.delegate.onAllotmentReleased();
34
23
  }
35
- waitForBacklog(context, signal) {
36
- return new Promise((resolve) => {
37
- let settled = false;
38
- const settle = (allotment) => {
39
- if (settled)
40
- return;
41
- settled = true;
42
- cleanup();
43
- resolve(allotment);
44
- };
45
- const waiter = {
46
- context,
47
- resolve: (allotment) => settle(allotment),
48
- };
49
- this.backlog.unshift(waiter);
50
- const timer = setTimeout(() => settle(undefined), this.getBacklogTimeout(context));
51
- const onAbort = () => settle(undefined);
52
- const cleanup = () => {
53
- clearTimeout(timer);
54
- signal?.removeEventListener("abort", onAbort);
55
- const idx = this.backlog.indexOf(waiter);
56
- if (idx !== -1) {
57
- this.backlog.splice(idx, 1);
58
- }
59
- };
60
- signal?.addEventListener("abort", onAbort, { once: true });
61
- });
24
+ onLimitChanged(oldLimit, newLimit) {
25
+ this.delegate.onLimitChanged(oldLimit, newLimit);
62
26
  }
63
27
  }
@@ -1,6 +1,8 @@
1
1
  import { Limiter, type LimiterOptions } from "../../Limiter.js";
2
+ import { type FifoBlockingRejectionOptions } from "../allocation-unavailable-strategies/FifoBlockingRejection.js";
2
3
  export declare function makeBlockingLimiter<ContextT = void>(options?: {
3
- timeout?: number;
4
+ backlogSize?: number;
5
+ backlogTimeout?: FifoBlockingRejectionOptions<ContextT>["backlogTimeout"];
4
6
  limiter?: Omit<LimiterOptions<ContextT>, "allotmentUnavailableStrategy">;
5
7
  }): Limiter<ContextT>;
6
8
  //# sourceMappingURL=makeBlockingLimiter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"makeBlockingLimiter.d.ts","sourceRoot":"","sources":["../../../src/limiter/factories/makeBlockingLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAG1B,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,IAAI,EACjD,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,8BAA8B,CAAC,CAAC;CACrE,GACL,OAAO,CAAC,QAAQ,CAAC,CAOnB"}
1
+ {"version":3,"file":"makeBlockingLimiter.d.ts","sourceRoot":"","sources":["../../../src/limiter/factories/makeBlockingLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,+DAA+D,CAAC;AAEvE,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,IAAI,EACjD,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,4BAA4B,CAAC,QAAQ,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC1E,OAAO,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,8BAA8B,CAAC,CAAC;CACrE,GACL,OAAO,CAAC,QAAQ,CAAC,CAQnB"}
@@ -1,10 +1,11 @@
1
1
  import { Limiter, } from "../../Limiter.js";
2
- import { FifoBlockingRejection } from "../allocation-unavailable-strategies/FifoBlockingRejection.js";
2
+ import { FifoBlockingRejection, } from "../allocation-unavailable-strategies/FifoBlockingRejection.js";
3
3
  export function makeBlockingLimiter(options = {}) {
4
4
  return new Limiter({
5
5
  ...options.limiter,
6
6
  allotmentUnavailableStrategy: new FifoBlockingRejection({
7
- timeout: options.timeout,
7
+ backlogSize: options.backlogSize,
8
+ backlogTimeout: options.backlogTimeout,
8
9
  }),
9
10
  });
10
11
  }
@@ -1,7 +1,7 @@
1
1
  import { Limiter, type LimiterOptions } from "../../Limiter.js";
2
2
  import { type LifoBlockingRejectionOptions } from "../allocation-unavailable-strategies/LifoBlockingRejection.js";
3
3
  export declare function makeLifoBlockingLimiter<ContextT = void>(options?: {
4
- backlogSize?: number;
4
+ backlogSize?: LifoBlockingRejectionOptions<ContextT>["backlogSize"];
5
5
  backlogTimeout?: LifoBlockingRejectionOptions<ContextT>["backlogTimeout"];
6
6
  limiter?: Omit<LimiterOptions<ContextT>, "allotmentUnavailableStrategy">;
7
7
  }): Limiter<ContextT>;