adaptive-concurrency 0.9.1 → 0.10.1

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/Limiter.d.ts CHANGED
@@ -121,6 +121,8 @@ export declare class Limiter<Context = void> {
121
121
  private readonly acquireFailedCounter;
122
122
  private readonly acquireBypassedCounter;
123
123
  private readonly limitGauge;
124
+ private readonly acquireTimeOnSuccessDistribution;
125
+ private readonly acquireTimeOnUnavailableDistribution;
124
126
  static makeDefaultLimit(): AdaptiveLimit;
125
127
  constructor(options?: LimiterOptions<Context>);
126
128
  acquire(options?: AcquireOptions<Context>): AcquireResult;
@@ -1 +1 @@
1
- {"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"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"}
package/dist/Limiter.js CHANGED
@@ -28,6 +28,8 @@ export class Limiter {
28
28
  acquireFailedCounter;
29
29
  acquireBypassedCounter;
30
30
  limitGauge;
31
+ acquireTimeOnSuccessDistribution;
32
+ acquireTimeOnUnavailableDistribution;
31
33
  static makeDefaultLimit() {
32
34
  return new GradientLimit();
33
35
  }
@@ -66,6 +68,8 @@ export class Limiter {
66
68
  this.acquireSucceededCounter = registry.counter(MetricIds.ACQUIRE_ATTEMPT_NAME, { id: limiterName, status: "succeeded" });
67
69
  this.acquireFailedCounter = registry.counter(MetricIds.ACQUIRE_ATTEMPT_NAME, { id: limiterName, status: "failed" });
68
70
  this.acquireBypassedCounter = registry.counter(MetricIds.ACQUIRE_ATTEMPT_NAME, { id: limiterName, status: "bypassed" });
71
+ this.acquireTimeOnSuccessDistribution = registry.distribution(MetricIds.ACQUIRE_TIME_NAME, { id: limiterName, status: "success" });
72
+ this.acquireTimeOnUnavailableDistribution = registry.distribution(MetricIds.ACQUIRE_TIME_NAME, { id: limiterName, status: "unavailable" });
69
73
  this.acquireBypassedAllotment = {
70
74
  releaseAndRecordSuccess: async () => {
71
75
  this.successCounter.increment();
@@ -88,6 +92,7 @@ export class Limiter {
88
92
  this.acquireBypassedCounter.increment();
89
93
  return this.acquireBypassedAllotment;
90
94
  }
95
+ const acquireStart = this.clock();
91
96
  const state = {
92
97
  limit: this._limit,
93
98
  inflight: this._inflight,
@@ -95,21 +100,32 @@ export class Limiter {
95
100
  if (!(await this.acquireStrategy.tryAcquireAllotment(ctx, state))) {
96
101
  this.acquireFailedCounter.increment();
97
102
  if (!this.rejectionStrategy) {
103
+ this.acquireTimeOnUnavailableDistribution.addSample(this.clock() - acquireStart);
98
104
  return undefined;
99
105
  }
100
- // if signal aborted here, nothing to cleanup, as we didn't acquire anything.
106
+ // if signal aborted here, nothing to cleanup, as we didn't acquire
107
+ // anything. Also, don't try to record the acquire time, as we aborted
108
+ // mid-way (didn't invoke the rejection strategy).
101
109
  if (options?.signal?.aborted) {
102
110
  return undefined;
103
111
  }
104
- return this.rejectionStrategy.onAllotmentUnavailable(ctx, async (retryCtx) => {
112
+ const result = await this.rejectionStrategy.onAllotmentUnavailable(ctx, async (retryCtx) => {
105
113
  if (options?.signal?.aborted) {
106
114
  return undefined;
107
115
  }
108
116
  return this.tryAcquireCore(retryCtx);
109
117
  }, options?.signal);
118
+ const distribution = result
119
+ ? this.acquireTimeOnSuccessDistribution
120
+ : this.acquireTimeOnUnavailableDistribution;
121
+ distribution.addSample(this.clock() - acquireStart);
122
+ return result;
110
123
  }
111
124
  this.acquireSucceededCounter.increment();
112
125
  const allotment = this.createAllotment(ctx);
126
+ // Record the acquire time as a success, since we did actually succeed even
127
+ // if we abort below.
128
+ this.acquireTimeOnSuccessDistribution.addSample(this.clock() - acquireStart);
113
129
  if (options?.signal?.aborted) {
114
130
  // here, we did acquire, so we need to try to cleanup.
115
131
  await allotment.releaseAndIgnore();
@@ -15,6 +15,7 @@ export declare const MetricIds: {
15
15
  readonly MIN_RTT_NAME: "min_rtt";
16
16
  readonly WINDOW_MIN_RTT_NAME: "min_window_rtt";
17
17
  readonly WINDOW_QUEUE_SIZE_NAME: "queue_size";
18
+ readonly ACQUIRE_TIME_NAME: "acquire_time";
18
19
  };
19
20
  /**
20
21
  * Listener to receive samples for a distribution.
@@ -1 +1 @@
1
- {"version":3,"file":"MetricRegistry.d.ts","sourceRoot":"","sources":["../src/MetricRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;;;;;;;;;CASZ,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,0FAA0F;AAC1F,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,YAAY,CACV,EAAE,EAAE,MAAM,EACV,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,kBAAkB,CAAC;IAEtB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAE9D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;CACnE;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAUhC,CAAC"}
1
+ {"version":3,"file":"MetricRegistry.d.ts","sourceRoot":"","sources":["../src/MetricRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;CAUZ,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,0FAA0F;AAC1F,MAAM,WAAW,KAAK;IACpB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,YAAY,CACV,EAAE,EAAE,MAAM,EACV,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,kBAAkB,CAAC;IAEtB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAE9D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;CACnE;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAUhC,CAAC"}
@@ -15,6 +15,7 @@ export const MetricIds = {
15
15
  MIN_RTT_NAME: "min_rtt",
16
16
  WINDOW_MIN_RTT_NAME: "min_window_rtt",
17
17
  WINDOW_QUEUE_SIZE_NAME: "queue_size",
18
+ ACQUIRE_TIME_NAME: "acquire_time",
18
19
  };
19
20
  const NOOP_SAMPLE_LISTENER = { addSample() { } };
20
21
  const NOOP_COUNTER = { increment() { } };
@@ -17,6 +17,16 @@ export interface AIMDLimitOptions {
17
17
  * Default: 5000
18
18
  */
19
19
  timeout?: number;
20
+ /**
21
+ * Absolute amount to use as a +/- jitter band around `backoffRatio` for
22
+ * each multiplicative decrease. Breaks lockstep oscillation when multiple
23
+ * independent clients share the same configuration.
24
+ *
25
+ * For example, with `backoffRatio: 0.9` and `backoffJitter: 0.02`, each
26
+ * decrease multiplies by a uniformly random value in [0.88, 0.92].
27
+ * Must be in [0, 0.05]. Default: 0.02.
28
+ */
29
+ backoffJitter?: number;
20
30
  }
21
31
  export declare class AIMDLimit implements AdaptiveLimit {
22
32
  private _limit;
@@ -25,6 +35,7 @@ export declare class AIMDLimit implements AdaptiveLimit {
25
35
  private readonly timeout;
26
36
  private readonly minLimit;
27
37
  private readonly maxLimit;
38
+ private readonly backoffJitter;
28
39
  constructor(options?: AIMDLimitOptions);
29
40
  addSample(_startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
30
41
  get currentLimit(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"AIMDLimit.d.ts","sourceRoot":"","sources":["../../src/limit/AIMDLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,SAAU,YAAW,aAAa;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,OAAO,GAAE,gBAAqB;IAiB1C,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAapF,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,QAAQ,IAAI,MAAM;CAGnB"}
1
+ {"version":3,"file":"AIMDLimit.d.ts","sourceRoot":"","sources":["../../src/limit/AIMDLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,SAAU,YAAW,aAAa;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,GAAE,gBAAqB;IA0B1C,SAAS,CACP,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACf,IAAI;IAuBP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,QAAQ,IAAI,MAAM;CAGnB"}
@@ -6,6 +6,7 @@ export class AIMDLimit {
6
6
  timeout;
7
7
  minLimit;
8
8
  maxLimit;
9
+ backoffJitter;
9
10
  constructor(options = {}) {
10
11
  const initialLimit = options.initialLimit ?? 20;
11
12
  this._limit = initialLimit;
@@ -13,19 +14,27 @@ export class AIMDLimit {
13
14
  this.timeout = options.timeout ?? 5_000;
14
15
  this.minLimit = options.minLimit ?? 20;
15
16
  this.maxLimit = options.maxLimit ?? 200;
17
+ this.backoffJitter = options.backoffJitter ?? 0.02;
16
18
  if (this.backoffRatio >= 1.0 || this.backoffRatio < 0.5) {
17
19
  throw new Error("Backoff ratio must be in the range [0.5, 1.0)");
18
20
  }
19
21
  if (this.timeout <= 0) {
20
22
  throw new Error("Timeout must be positive");
21
23
  }
24
+ if (this.backoffJitter < 0 || this.backoffJitter > 0.05) {
25
+ throw new Error("backoffJitter must be in the range [0, 0.05]");
26
+ }
27
+ if (this.backoffRatio + this.backoffJitter >= 1.0) {
28
+ throw new Error("backoffRatio + backoffJitter must be < 1.0 to guarantee the limit decreases on drop");
29
+ }
22
30
  }
23
31
  addSample(_startTime, rtt, inflight, didDrop) {
24
32
  let currentLimit = this._limit;
25
33
  if (didDrop || rtt > this.timeout) {
26
- currentLimit = Math.floor(currentLimit * this.backoffRatio);
34
+ const jitteredRatio = Math.max(0.5, Math.min(1 - Number.EPSILON, this.backoffRatio + (Math.random() * 2 - 1) * this.backoffJitter));
35
+ currentLimit = Math.floor(currentLimit * jitteredRatio);
27
36
  }
28
- else if (inflight * 2 >= currentLimit) {
37
+ else if (inflight >= 0.5 * currentLimit) {
29
38
  currentLimit = currentLimit + 1;
30
39
  }
31
40
  const newLimit = Math.min(this.maxLimit, Math.max(this.minLimit, currentLimit));
@@ -0,0 +1,78 @@
1
+ import type { AdaptiveLimit } from "./StreamingLimit.js";
2
+ /**
3
+ * Decorator that normalizes RTT samples by operation group before forwarding
4
+ * them to a delegate {@link AdaptiveLimit}.
5
+ *
6
+ * When a single limiter covers heterogeneous operations (e.g. fast reads and
7
+ * slow writes), raw RTT variance can reflect the **operation mix** rather than
8
+ * downstream congestion. A shift toward slower operations looks like a latency
9
+ * spike and causes the delegate to needlessly reduce the limit.
10
+ *
11
+ * `OperationGroupedLimit` solves this by maintaining a per-group RTT baseline
12
+ * (exponential moving average with a long window). Each sample's RTT is
13
+ * expressed as a ratio relative to its group's own baseline, then scaled by a
14
+ * fixed reference value:
15
+ *
16
+ * normalizedRtt = (rtt / groupBaseline) × referenceRtt
17
+ *
18
+ * `referenceRtt` is a constant configured at construction time (derived from
19
+ * the first group's baseline after warmup if not explicitly provided). Because
20
+ * it never changes, a shift in operation mix cannot cause a spurious RTT spike.
21
+ * Meanwhile, real congestion within any group shows through because it causes
22
+ * `rtt / groupBaseline > 1`.
23
+ *
24
+ * Per-group baselines use a long EMA window so they represent the group's
25
+ * **intrinsic** latency characteristic and don't absorb short-term congestion.
26
+ *
27
+ * The `groupFor` function maps each sample to a group key. Samples whose group
28
+ * cannot be determined (returns `undefined`) are forwarded with their raw RTT.
29
+ */
30
+ export declare class OperationGroupedLimit implements AdaptiveLimit {
31
+ private readonly delegate;
32
+ private readonly groupFor;
33
+ private readonly groups;
34
+ private readonly groupWindow;
35
+ private readonly groupWarmup;
36
+ /**
37
+ * Fixed reference RTT used as the absolute scale for normalized values.
38
+ * Once set, it never changes, ensuring mix shifts don't affect normalization.
39
+ * `undefined` until enough samples have been seen to establish it.
40
+ */
41
+ private referenceRtt;
42
+ private readonly configuredReferenceRtt;
43
+ constructor(delegate: AdaptiveLimit, options: {
44
+ /**
45
+ * Maps a sample's `startTime` to its operation group key.
46
+ *
47
+ * In practice, the caller records the group when acquiring a limiter
48
+ * allotment (at which point `startTime` is captured by the `Limiter`),
49
+ * then provides a lookup here that resolves that `startTime` back to the
50
+ * group. Returning `undefined` forwards the sample unnormalized.
51
+ */
52
+ groupFor: (startTime: number) => string | undefined;
53
+ /**
54
+ * EMA window size for per-group RTT baselines. These should be long
55
+ * enough to represent the group's intrinsic latency so short-term
56
+ * congestion shows through as deviations. Default: 500
57
+ */
58
+ groupWindow?: number;
59
+ /**
60
+ * Number of initial samples averaged arithmetically before switching to
61
+ * exponential decay (per-group). Default: 10
62
+ */
63
+ groupWarmup?: number;
64
+ /**
65
+ * Fixed reference RTT (in milliseconds) used as the scale for all
66
+ * normalized values. If not provided, it is automatically set from the
67
+ * first group's baseline after its warmup phase completes.
68
+ */
69
+ referenceRtt?: number;
70
+ });
71
+ addSample(startTime: number, rtt: number, inflight: number, didDrop: boolean): void;
72
+ get currentLimit(): number;
73
+ subscribe(consumer: (newLimit: number) => void, options?: {
74
+ signal?: AbortSignal;
75
+ }): () => void;
76
+ toString(): string;
77
+ }
78
+ //# sourceMappingURL=OperationGroupedLimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OperationGroupedLimit.d.ts","sourceRoot":"","sources":["../../src/limit/OperationGroupedLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4C;IACrE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAqB;gBAG1D,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE;QACP;;;;;;;WAOG;QACH,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;QAEpD;;;;WAIG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC;QAErB;;;WAGG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC;QAErB;;;;WAIG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB;IAUH,SAAS,CACP,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACf,IAAI;IAqCP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACjC,MAAM,IAAI;IAIb,QAAQ,IAAI,MAAM;CAOnB"}
@@ -0,0 +1,93 @@
1
+ import { ExpMovingAverage } from "../statistics/ExpMovingAverage.js";
2
+ /**
3
+ * Decorator that normalizes RTT samples by operation group before forwarding
4
+ * them to a delegate {@link AdaptiveLimit}.
5
+ *
6
+ * When a single limiter covers heterogeneous operations (e.g. fast reads and
7
+ * slow writes), raw RTT variance can reflect the **operation mix** rather than
8
+ * downstream congestion. A shift toward slower operations looks like a latency
9
+ * spike and causes the delegate to needlessly reduce the limit.
10
+ *
11
+ * `OperationGroupedLimit` solves this by maintaining a per-group RTT baseline
12
+ * (exponential moving average with a long window). Each sample's RTT is
13
+ * expressed as a ratio relative to its group's own baseline, then scaled by a
14
+ * fixed reference value:
15
+ *
16
+ * normalizedRtt = (rtt / groupBaseline) × referenceRtt
17
+ *
18
+ * `referenceRtt` is a constant configured at construction time (derived from
19
+ * the first group's baseline after warmup if not explicitly provided). Because
20
+ * it never changes, a shift in operation mix cannot cause a spurious RTT spike.
21
+ * Meanwhile, real congestion within any group shows through because it causes
22
+ * `rtt / groupBaseline > 1`.
23
+ *
24
+ * Per-group baselines use a long EMA window so they represent the group's
25
+ * **intrinsic** latency characteristic and don't absorb short-term congestion.
26
+ *
27
+ * The `groupFor` function maps each sample to a group key. Samples whose group
28
+ * cannot be determined (returns `undefined`) are forwarded with their raw RTT.
29
+ */
30
+ export class OperationGroupedLimit {
31
+ delegate;
32
+ groupFor;
33
+ groups = new Map();
34
+ groupWindow;
35
+ groupWarmup;
36
+ /**
37
+ * Fixed reference RTT used as the absolute scale for normalized values.
38
+ * Once set, it never changes, ensuring mix shifts don't affect normalization.
39
+ * `undefined` until enough samples have been seen to establish it.
40
+ */
41
+ referenceRtt;
42
+ configuredReferenceRtt;
43
+ constructor(delegate, options) {
44
+ this.delegate = delegate;
45
+ this.groupFor = options.groupFor;
46
+ this.groupWindow = options.groupWindow ?? 500;
47
+ this.groupWarmup = options.groupWarmup ?? 10;
48
+ this.configuredReferenceRtt = options.referenceRtt;
49
+ this.referenceRtt = options.referenceRtt;
50
+ }
51
+ addSample(startTime, rtt, inflight, didDrop) {
52
+ const group = this.groupFor(startTime);
53
+ if (group === undefined) {
54
+ this.delegate.addSample(startTime, rtt, inflight, didDrop);
55
+ return;
56
+ }
57
+ let groupEma = this.groups.get(group);
58
+ let isNew = false;
59
+ if (!groupEma) {
60
+ groupEma = new ExpMovingAverage(this.groupWindow, this.groupWarmup);
61
+ this.groups.set(group, groupEma);
62
+ isNew = true;
63
+ }
64
+ // Read the group baseline *before* incorporating this sample so that the
65
+ // current observation doesn't immediately anchor its own normalization.
66
+ const prevGroupBaseline = groupEma.currentValue;
67
+ groupEma.addSample(rtt);
68
+ // Auto-detect the reference RTT from the first group's baseline once its
69
+ // warmup is complete.
70
+ if (this.referenceRtt === undefined && !isNew && prevGroupBaseline > 0) {
71
+ this.referenceRtt = prevGroupBaseline;
72
+ }
73
+ let normalizedRtt;
74
+ if (prevGroupBaseline <= 0 || this.referenceRtt === undefined) {
75
+ normalizedRtt = rtt;
76
+ }
77
+ else {
78
+ normalizedRtt = (rtt / prevGroupBaseline) * this.referenceRtt;
79
+ }
80
+ this.delegate.addSample(startTime, normalizedRtt, inflight, didDrop);
81
+ }
82
+ get currentLimit() {
83
+ return this.delegate.currentLimit;
84
+ }
85
+ subscribe(consumer, options) {
86
+ return this.delegate.subscribe(consumer, options);
87
+ }
88
+ toString() {
89
+ return (`OperationGroupedLimit [groups=${this.groups.size}` +
90
+ `, referenceRtt=${this.referenceRtt?.toFixed(3) ?? "pending"}` +
91
+ `, delegate=${this.delegate}]`);
92
+ }
93
+ }
@@ -43,9 +43,10 @@ export interface VegasLimitOptions {
43
43
  */
44
44
  increase?(limit: number): number;
45
45
  /**
46
- * Compute the new limit when decreasing.
46
+ * Compute the new limit when decreasing. If didDrop is true, decrease() is
47
+ * being called because of a drop.
47
48
  */
48
- decrease?(limit: number): number;
49
+ decrease?(limit: number, didDrop: boolean): number;
49
50
  };
50
51
  /**
51
52
  * The limiter will probe for a new noload RTT every
@@ -66,9 +67,9 @@ export declare class VegasLimit implements AdaptiveLimit {
66
67
  private readonly smoothing;
67
68
  private readonly policy;
68
69
  private readonly rttSampleListener;
69
- private readonly probeMultiplier;
70
70
  private probeCount;
71
71
  private probeJitter;
72
+ private readonly probeMultiplier;
72
73
  constructor(options?: VegasLimitOptions);
73
74
  private resetProbeJitter;
74
75
  private shouldProbe;
@@ -1 +1 @@
1
- {"version":3,"file":"VegasLimit.d.ts","sourceRoot":"","sources":["../../src/limit/VegasLimit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAezD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE;QACP;;;;WAIG;QACH,KAAK,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAE9B;;;;WAIG;QACH,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAE7B;;WAEG;QACH,SAAS,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAElC;;WAEG;QACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAEjC;;WAEG;QACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;KAClC,CAAC;IAEF;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAElF,qBAAa,UAAW,YAAW,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,SAAS,CAAK;IAEtB,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,GAAE,iBAAsB;IAuB3C,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,WAAW;IAInB,SAAS,CACP,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACf,IAAI;IAIP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,OAAO,CAAC,gBAAgB;IA6BxB,OAAO,CAAC,oBAAoB;IA0C5B,QAAQ,IAAI,MAAM;CAGnB"}
1
+ {"version":3,"file":"VegasLimit.d.ts","sourceRoot":"","sources":["../../src/limit/VegasLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAezD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,MAAM,CAAC,EAAE;QACP;;;;WAIG;QACH,KAAK,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAE9B;;;;WAIG;QACH,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAE7B;;WAEG;QACH,SAAS,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAElC;;WAEG;QACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAEjC;;;WAGG;QACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;KACpD,CAAC;IAEF;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CACrC,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CACzC,CAAC;AAEF,qBAAa,UAAW,YAAW,aAAa;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,SAAS,CAAK;IAEtB,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IAEvD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,OAAO,GAAE,iBAAsB;IAyB3C,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,WAAW;IAOnB,SAAS,CACP,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACf,IAAI;IAMP,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,OAAO,CAAC,aAAa;IAOrB,SAAS,CACP,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACpC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,MAAM,IAAI;IAIb,OAAO,CAAC,gBAAgB;IA+BxB,OAAO,CAAC,oBAAoB;IA2C5B,QAAQ,IAAI,MAAM;CAGnB"}
@@ -1,6 +1,5 @@
1
1
  import { ListenerSet } from "../ListenerSet.js";
2
- import { MetricIds } from "../MetricRegistry.js";
3
- import { NoopMetricRegistry } from "../MetricRegistry.js";
2
+ import { MetricIds, NoopMetricRegistry } from "../MetricRegistry.js";
4
3
  /**
5
4
  * Sublinear scale of the concurrency limit (floor of log10, lower-bounded for
6
5
  * small n). Same idea as Netflix's Log10RootIntFunction. Used only for
@@ -21,9 +20,9 @@ export class VegasLimit {
21
20
  smoothing;
22
21
  policy;
23
22
  rttSampleListener;
24
- probeMultiplier;
25
23
  probeCount = 0;
26
24
  probeJitter;
25
+ probeMultiplier;
27
26
  constructor(options = {}) {
28
27
  const initialLimit = options.initialLimit ?? 20;
29
28
  this._limit = initialLimit;
@@ -47,7 +46,8 @@ export class VegasLimit {
47
46
  return this.probeJitter;
48
47
  }
49
48
  shouldProbe() {
50
- return this.probeJitter * this.probeMultiplier * this.estimatedLimit <= this.probeCount;
49
+ return (this.probeJitter * this.probeMultiplier * this.estimatedLimit <=
50
+ this.probeCount);
51
51
  }
52
52
  addSample(startTime, rtt, inflight, didDrop) {
53
53
  this.applyNewLimit(this.computeNextLimit(startTime, rtt, inflight, didDrop));
@@ -73,11 +73,13 @@ export class VegasLimit {
73
73
  this.resetProbeJitter();
74
74
  this.probeCount = 0;
75
75
  this.rttNoload = rtt;
76
+ this.rttSampleListener.addSample(rtt);
76
77
  return Math.floor(this.estimatedLimit);
77
78
  }
78
79
  const rttNoload = this.rttNoload;
79
80
  if (rttNoload === 0 || rtt < rttNoload) {
80
81
  this.rttNoload = rtt;
82
+ this.rttSampleListener.addSample(rtt);
81
83
  return Math.floor(this.estimatedLimit);
82
84
  }
83
85
  this.rttSampleListener.addSample(rttNoload);
@@ -89,7 +91,7 @@ export class VegasLimit {
89
91
  let newLimit;
90
92
  // Treat any drop (i.e timeout) as needing to reduce the limit
91
93
  if (didDrop) {
92
- newLimit = this.policy.decrease(estimatedLimit);
94
+ newLimit = this.policy.decrease(estimatedLimit, didDrop);
93
95
  // Prevent upward drift if not close to the limit
94
96
  }
95
97
  else if (inflight * 2 < estimatedLimit) {
@@ -109,7 +111,7 @@ export class VegasLimit {
109
111
  // Detecting latency so decrease
110
112
  }
111
113
  else if (queueSize > beta) {
112
- newLimit = this.policy.decrease(estimatedLimit);
114
+ newLimit = this.policy.decrease(estimatedLimit, didDrop);
113
115
  // We're within the sweet spot so nothing to do
114
116
  }
115
117
  else {
@@ -117,7 +119,8 @@ export class VegasLimit {
117
119
  }
118
120
  }
119
121
  newLimit = Math.max(1, Math.min(this.maxLimit, newLimit));
120
- newLimit = (1 - this.smoothing) * estimatedLimit + this.smoothing * newLimit;
122
+ newLimit =
123
+ (1 - this.smoothing) * estimatedLimit + this.smoothing * newLimit;
121
124
  this.estimatedLimit = newLimit;
122
125
  return Math.floor(newLimit);
123
126
  }
@@ -0,0 +1,30 @@
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
+ };
15
+ /**
16
+ * Rejection strategy that blocks the caller in a FIFO queue when the limit
17
+ * has been reached, waiting for a slot to open up. This strategy favors
18
+ * fairness: callers are served in the order they arrived.
19
+ *
20
+ * Because JavaScript is single-threaded, "blocking" means awaiting a promise
21
+ * that resolves when a token becomes available.
22
+ */
23
+ export declare class FifoBlockingRejection<ContextT> implements AllotmentUnavailableStrategy<ContextT> {
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;
29
+ }
30
+ //# sourceMappingURL=FifoBlockingRejection.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,29 @@
1
+ import { LinkedWaiterQueue } from "../../utils/LinkedWaiterQueue.js";
2
+ import { BlockingBacklogRejection, MAX_TIMEOUT, } from "./BlockingBacklogRejection.js";
3
+ /**
4
+ * Rejection strategy that blocks the caller in a FIFO queue when the limit
5
+ * has been reached, waiting for a slot to open up. This strategy favors
6
+ * fairness: callers are served in the order they arrived.
7
+ *
8
+ * Because JavaScript is single-threaded, "blocking" means awaiting a promise
9
+ * that resolves when a token becomes available.
10
+ */
11
+ export class FifoBlockingRejection {
12
+ delegate;
13
+ constructor(options = {}) {
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
+ }
20
+ onAllotmentUnavailable(context, retry, signal) {
21
+ return this.delegate.onAllotmentUnavailable(context, retry, signal);
22
+ }
23
+ onAllotmentReleased() {
24
+ return this.delegate.onAllotmentReleased();
25
+ }
26
+ onLimitChanged(oldLimit, newLimit) {
27
+ this.delegate.onLimitChanged(oldLimit, newLimit);
28
+ }
29
+ }
@@ -0,0 +1,27 @@
1
+ import type { AcquireResult, AllotmentUnavailableStrategy } from "../../Limiter.js";
2
+ export interface LifoBlockingRejectionOptions<ContextT> {
3
+ /**
4
+ * Maximum number of blocked callers in the backlog. Default: 100
5
+ */
6
+ backlogSize?: number | undefined;
7
+ /**
8
+ * Maximum timeout for callers blocked on the limiter, in milliseconds.
9
+ * Can be a fixed number or a function that derives the timeout from the
10
+ * request context (e.g. from a deadline). Default: 1000
11
+ */
12
+ backlogTimeout?: number | ((context: ContextT) => number) | undefined;
13
+ }
14
+ /**
15
+ * Rejection strategy that blocks the caller in a LIFO queue when the limit
16
+ * has been reached. This strategy favors availability over latency by
17
+ * processing the most recently blocked request first, keeping success
18
+ * latencies low and minimizing timeouts.
19
+ */
20
+ export declare class LifoBlockingRejection<ContextT> implements AllotmentUnavailableStrategy<ContextT> {
21
+ private readonly delegate;
22
+ constructor(options?: LifoBlockingRejectionOptions<ContextT>);
23
+ onAllotmentUnavailable(context: ContextT, retry: (context: ContextT) => AcquireResult, signal?: AbortSignal): Promise<import("../../LimitAllotment.js").LimitAllotment | undefined>;
24
+ onAllotmentReleased(): Promise<void>;
25
+ onLimitChanged(oldLimit: number, newLimit: number): void;
26
+ }
27
+ //# sourceMappingURL=LifoBlockingRejection.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,27 @@
1
+ import { LinkedWaiterQueue } from "../../utils/LinkedWaiterQueue.js";
2
+ import { BlockingBacklogRejection, } from "./BlockingBacklogRejection.js";
3
+ /**
4
+ * Rejection strategy that blocks the caller in a LIFO queue when the limit
5
+ * has been reached. This strategy favors availability over latency by
6
+ * processing the most recently blocked request first, keeping success
7
+ * latencies low and minimizing timeouts.
8
+ */
9
+ export class LifoBlockingRejection {
10
+ delegate;
11
+ constructor(options = {}) {
12
+ this.delegate = new BlockingBacklogRejection({
13
+ backlogSize: options.backlogSize ?? 100,
14
+ backlogTimeout: options.backlogTimeout ?? 1_000,
15
+ queue: new LinkedWaiterQueue("front"),
16
+ });
17
+ }
18
+ onAllotmentUnavailable(context, retry, signal) {
19
+ return this.delegate.onAllotmentUnavailable(context, retry, signal);
20
+ }
21
+ onAllotmentReleased() {
22
+ return this.delegate.onAllotmentReleased();
23
+ }
24
+ onLimitChanged(oldLimit, newLimit) {
25
+ this.delegate.onLimitChanged(oldLimit, newLimit);
26
+ }
27
+ }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "dynamic rate limiting"
10
10
  ],
11
11
  "author": "Ethan Resnick <ethan.resnick@gmail.com>",
12
- "version": "0.9.1",
12
+ "version": "0.10.1",
13
13
  "type": "module",
14
14
  "exports": {
15
15
  ".": {