adaptive-concurrency 0.3.2 → 0.3.3

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 (41) 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 +14 -21
  4. package/dist/Limiter.d.ts.map +1 -1
  5. package/dist/Limiter.js +31 -47
  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 +51 -0
  19. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.d.ts.map +1 -0
  20. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.js +176 -0
  21. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts +1 -1
  22. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts.map +1 -1
  23. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js +6 -0
  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 +12 -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 +12 -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/package.json +3 -2
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Handle returned when a concurrency slot is acquired. The caller must invoke
3
- * exactly one of the release-and-record methods when the operation completes.
3
+ * exactly one of the release-and-xxx methods when the operation completes.
4
+ *
5
+ * **Important for implementers:** the release methods below must never throw
6
+ * or produce a rejected promise. If an error occurs internally (e.g. notifying
7
+ * a strategy), the implementation is responsible for catching and handling it.
8
+ * Callers may invoke these methods in a fire-and-forget manner (without
9
+ * awaiting), so any rejection would become an unhandled promise rejection.
4
10
  */
5
11
  export interface LimitAllotment {
6
12
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"LimitAllotment.d.ts","sourceRoot":"","sources":["../src/LimitAllotment.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;;OAGG;IACH,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC;;;;OAIG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
1
+ {"version":3,"file":"LimitAllotment.d.ts","sourceRoot":"","sources":["../src/LimitAllotment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;;OAGG;IACH,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC;;;;OAIG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
package/dist/Limiter.d.ts CHANGED
@@ -9,7 +9,7 @@ export type MaybePromise<T> = T | Promise<T>;
9
9
  export type AcquireResult = Promise<LimitAllotment | undefined>;
10
10
  export interface AcquireOptions<ContextT = void> {
11
11
  context?: ContextT;
12
- signal?: AbortSignal;
12
+ signal?: AbortSignal | undefined;
13
13
  }
14
14
  /**
15
15
  * Read-only view of the limiter's current state, provided to strategies so
@@ -40,13 +40,13 @@ export interface AcquireStrategy<ContextT> {
40
40
  * Called when the adaptive limit changes. The strategy can react
41
41
  * (e.g. adjust available permits by the delta, update partition sub-limits).
42
42
  */
43
- onLimitChanged?(oldLimit: number, newLimit: number): MaybePromise<void>;
43
+ onLimitChanged?(oldLimit: number, newLimit: number): void;
44
44
  }
45
45
  /**
46
46
  * Determines what happens when a request is rejected by the
47
- * {@link AcquireStrategy}. The type parameter `ResultT` flows through to
48
- * {@link Limiter.acquire}'s return type, enabling the type system to
49
- * distinguish sync limiters (no promise) from async/blocking ones.
47
+ * {@link AcquireStrategy} (e.g. queue the caller, reject immediately, or block
48
+ * until a slot is available). Implementations must return an
49
+ * {@link AcquireResult}, which is always a `Promise`.
50
50
  */
51
51
  export interface AllotmentUnavailableStrategy<ContextT> {
52
52
  /**
@@ -66,6 +66,11 @@ export interface AllotmentUnavailableStrategy<ContextT> {
66
66
  * Blocking strategies use this to wake queued waiters.
67
67
  */
68
68
  onAllotmentReleased(): MaybePromise<void>;
69
+ /**
70
+ * Called when the adaptive limit changes. Blocking strategies can use this to
71
+ * proactively drain queued waiters when capacity increases.
72
+ */
73
+ onLimitChanged?(oldLimit: number, newLimit: number): MaybePromise<void>;
69
74
  }
70
75
  export interface LimiterOptions<ContextT> {
71
76
  limit?: AdaptiveLimit;
@@ -97,8 +102,6 @@ export interface LimiterOptions<ContextT> {
97
102
  * Concurrency limiter with pluggable strategies for gating decisions and
98
103
  * rejection handling.
99
104
  *
100
- * `acquire()` always returns a `Promise<LimitAllotment | undefined>`.
101
- *
102
105
  * @typeParam ContextT Request context type (e.g. partition key).
103
106
  */
104
107
  export declare class Limiter<Context = void> {
@@ -122,25 +125,13 @@ export declare class Limiter<Context = void> {
122
125
  getLimit(): number;
123
126
  getInflight(): number;
124
127
  }
125
- /**
126
- * Simple semaphore-based acquire strategy. Tracks a permits counter that is
127
- * decremented when an allotment is taken and incremented on release. When
128
- * permits reach zero, {@link tryAcquireAllotment} returns false.
129
- */
130
- export declare class SemaphoreStrategy {
131
- private permits;
132
- constructor(initialLimit: number);
133
- tryAcquireAllotment(): boolean;
134
- onAllotmentReleased(): void;
135
- onLimitChanged(oldLimit: number, newLimit: number): void;
136
- }
137
128
  export type RunCallbackArgs<ContextT> = {
138
129
  context: ContextT | undefined;
139
130
  signal: AbortSignal | undefined;
140
131
  };
141
132
  export interface LimitedFunction<ContextT> {
142
- <T, E extends Error = Error>(fn: (args: RunCallbackArgs<ContextT>) => RunResult<T, E> | Promise<RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
143
- <T, E extends Error = Error>(options: AcquireOptions<ContextT>, fn: (args: RunCallbackArgs<ContextT>) => RunResult<T, E> | Promise<RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
133
+ <T, E extends Error = Error>(fn: (args: RunCallbackArgs<ContextT>) => T | RunResult<T, E> | Promise<T | RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
134
+ <T, E extends Error = Error>(options: AcquireOptions<ContextT>, fn: (args: RunCallbackArgs<ContextT>) => T | RunResult<T, E> | Promise<T | RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
144
135
  }
145
136
  /**
146
137
  * Creates a helper that runs callbacks under acquired limiter allotments.
@@ -150,6 +141,8 @@ export interface LimitedFunction<ContextT> {
150
141
  * - On {@link RunResult} `success` / `ignore`, returns the carried value after
151
142
  * recording success/ignore to the allotment.
152
143
  * - On `dropped`, records drop and throws the carried error.
144
+ * - If callback returns a non-{@link RunResult} value, it is treated as
145
+ * implicit success and returned as-is.
153
146
  * - On uncaught exceptions from `fn`, records ignore and rethrows, except for
154
147
  * {@link AdaptiveTimeoutError}, which records drop and rethrows.
155
148
  * - Callback receives `{ context, signal }` from acquire options.
@@ -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
@@ -1,6 +1,7 @@
1
1
  import { GradientLimit } from "./limit/GradientLimit.js";
2
+ import { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
2
3
  import { MetricIds, NoopMetricRegistry } from "./MetricRegistry.js";
3
- import { isAdaptiveTimeoutError, QuotaNotAvailable, } from "./RunResult.js";
4
+ import { isAdaptiveTimeoutError, isRunResult, QuotaNotAvailable, } from "./RunResult.js";
4
5
  // ---------------------------------------------------------------------------
5
6
  // Limiter options
6
7
  // ---------------------------------------------------------------------------
@@ -14,8 +15,6 @@ let idCounter = 0;
14
15
  * Concurrency limiter with pluggable strategies for gating decisions and
15
16
  * rejection handling.
16
17
  *
17
- * `acquire()` always returns a `Promise<LimitAllotment | undefined>`.
18
- *
19
18
  * @typeParam ContextT Request context type (e.g. partition key).
20
19
  */
21
20
  export class Limiter {
@@ -45,7 +44,8 @@ export class Limiter {
45
44
  this.limitAlgorithm.subscribe((newLimit) => {
46
45
  const oldLimit = this._limit;
47
46
  this._limit = newLimit;
48
- void Promise.resolve(this.acquireStrategy.onLimitChanged?.(oldLimit, newLimit));
47
+ this.acquireStrategy.onLimitChanged?.(oldLimit, newLimit);
48
+ void this.rejectionStrategy?.onLimitChanged?.(oldLimit, newLimit);
49
49
  });
50
50
  const registry = options.metricRegistry ?? NoopMetricRegistry;
51
51
  const limiterName = options.name ?? `unnamed-${++idCounter}`;
@@ -86,6 +86,9 @@ export class Limiter {
86
86
  if (!(await this.acquireStrategy.tryAcquireAllotment(ctx, state))) {
87
87
  this.rejectedCounter.increment();
88
88
  if (this.rejectionStrategy) {
89
+ if (options?.signal?.aborted) {
90
+ return undefined;
91
+ }
89
92
  return this.rejectionStrategy.onAllotmentUnavailable(ctx, (retryCtx) => this.tryAcquireCore(retryCtx), options?.signal);
90
93
  }
91
94
  return undefined;
@@ -107,10 +110,12 @@ export class Limiter {
107
110
  const currentInflight = ++this._inflight;
108
111
  return {
109
112
  releaseAndRecordSuccess: async () => {
113
+ const endTime = this.clock();
114
+ const rtt = endTime - startTime;
110
115
  this._inflight--;
111
116
  await this.acquireStrategy.onAllotmentReleased(ctx);
112
117
  this.successCounter.increment();
113
- this.limitAlgorithm.addSample(startTime, this.clock() - startTime, currentInflight, false);
118
+ this.limitAlgorithm.addSample(startTime, rtt, currentInflight, false);
114
119
  await this.rejectionStrategy?.onAllotmentReleased();
115
120
  },
116
121
  releaseAndIgnore: async () => {
@@ -120,10 +125,12 @@ export class Limiter {
120
125
  await this.rejectionStrategy?.onAllotmentReleased();
121
126
  },
122
127
  releaseAndRecordDropped: async () => {
128
+ const endTime = this.clock();
129
+ const rtt = endTime - startTime;
123
130
  this._inflight--;
124
131
  await this.acquireStrategy.onAllotmentReleased(ctx);
125
132
  this.droppedCounter.increment();
126
- this.limitAlgorithm.addSample(startTime, this.clock() - startTime, currentInflight, true);
133
+ this.limitAlgorithm.addSample(startTime, rtt, currentInflight, true);
127
134
  await this.rejectionStrategy?.onAllotmentReleased();
128
135
  },
129
136
  };
@@ -135,32 +142,6 @@ export class Limiter {
135
142
  return this._inflight;
136
143
  }
137
144
  }
138
- // ---------------------------------------------------------------------------
139
- // Built-in acquire strategy: Semaphore
140
- // ---------------------------------------------------------------------------
141
- /**
142
- * Simple semaphore-based acquire strategy. Tracks a permits counter that is
143
- * decremented when an allotment is taken and incremented on release. When
144
- * permits reach zero, {@link tryAcquireAllotment} returns false.
145
- */
146
- export class SemaphoreStrategy {
147
- permits;
148
- constructor(initialLimit) {
149
- this.permits = initialLimit;
150
- }
151
- tryAcquireAllotment() {
152
- if (this.permits <= 0)
153
- return false;
154
- this.permits--;
155
- return true;
156
- }
157
- onAllotmentReleased() {
158
- this.permits++;
159
- }
160
- onLimitChanged(oldLimit, newLimit) {
161
- this.permits += newLimit - oldLimit;
162
- }
163
- }
164
145
  /**
165
146
  * Creates a helper that runs callbacks under acquired limiter allotments.
166
147
  *
@@ -169,28 +150,26 @@ export class SemaphoreStrategy {
169
150
  * - On {@link RunResult} `success` / `ignore`, returns the carried value after
170
151
  * recording success/ignore to the allotment.
171
152
  * - On `dropped`, records drop and throws the carried error.
153
+ * - If callback returns a non-{@link RunResult} value, it is treated as
154
+ * implicit success and returned as-is.
172
155
  * - On uncaught exceptions from `fn`, records ignore and rethrows, except for
173
156
  * {@link AdaptiveTimeoutError}, which records drop and rethrows.
174
157
  * - Callback receives `{ context, signal }` from acquire options.
175
158
  */
176
159
  export function withLimiter(limiter) {
177
160
  async function limited(optionsOrFn, maybeFn) {
178
- const hasOptions = maybeFn !== undefined;
179
- const options = hasOptions
180
- ? optionsOrFn
181
- : undefined;
182
- const fn = hasOptions
183
- ? maybeFn
184
- : optionsOrFn;
161
+ const [options, fn] = typeof optionsOrFn === "function"
162
+ ? [undefined, optionsOrFn]
163
+ : [optionsOrFn, maybeFn];
164
+ if (!fn) {
165
+ throw new Error("No function provided");
166
+ }
185
167
  const allotment = await limiter.acquire(options);
186
168
  if (!allotment) {
187
169
  return QuotaNotAvailable;
188
170
  }
189
171
  const [result] = await Promise.allSettled([
190
- fn({
191
- context: options?.context,
192
- signal: options?.signal,
193
- }),
172
+ fn({ context: options?.context, signal: options?.signal }),
194
173
  ]);
195
174
  if (result.status === "rejected") {
196
175
  if (isAdaptiveTimeoutError(result.reason)) {
@@ -202,16 +181,21 @@ export function withLimiter(limiter) {
202
181
  throw result.reason;
203
182
  }
204
183
  const outcome = result.value;
205
- switch (outcome.kind) {
184
+ if (!isRunResult(outcome)) {
185
+ await allotment.releaseAndRecordSuccess();
186
+ return outcome;
187
+ }
188
+ const outcomeCast = outcome;
189
+ switch (outcomeCast.kind) {
206
190
  case "success":
207
191
  await allotment.releaseAndRecordSuccess();
208
- return outcome.value;
192
+ return outcomeCast.value;
209
193
  case "ignore":
210
194
  await allotment.releaseAndIgnore();
211
- return outcome.value;
195
+ return outcomeCast.value;
212
196
  case "dropped":
213
197
  await allotment.releaseAndRecordDropped();
214
- throw outcome.error;
198
+ throw outcomeCast.error;
215
199
  }
216
200
  }
217
201
  return limited;
@@ -14,20 +14,26 @@ export declare class AdaptiveTimeoutError extends Error {
14
14
  constructor(message?: string);
15
15
  }
16
16
  export declare function isAdaptiveTimeoutError(error: unknown): error is AdaptiveTimeoutError;
17
- export interface RunSuccess<T> {
17
+ declare const isRunResultTag: unique symbol;
18
+ export type RunSuccess<T> = {
18
19
  readonly kind: "success";
20
+ readonly [isRunResultTag]: true;
19
21
  readonly value: T;
20
- }
21
- export interface RunIgnore<T> {
22
+ };
23
+ export type RunIgnore<T> = {
22
24
  readonly kind: "ignore";
25
+ readonly [isRunResultTag]: true;
23
26
  readonly value: T;
24
- }
25
- export interface RunDropped<E extends Error = Error> {
27
+ };
28
+ export type RunDropped<E extends Error = Error> = {
26
29
  readonly kind: "dropped";
30
+ readonly [isRunResultTag]: true;
27
31
  readonly error: E;
28
- }
32
+ };
29
33
  export type RunResult<T, E extends Error = Error> = RunSuccess<T> | RunIgnore<T> | RunDropped<E>;
34
+ export declare function isRunResult<T extends unknown>(value: T): value is T & RunResult<unknown, Error>;
30
35
  export declare function success<T>(value: T): RunSuccess<T>;
31
36
  export declare function ignore<T>(value: T): RunIgnore<T>;
32
37
  export declare function dropped<E extends Error>(error: E): RunDropped<E>;
38
+ export {};
33
39
  //# sourceMappingURL=RunResult.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunResult.d.ts","sourceRoot":"","sources":["../src/RunResult.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAE7D;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,qBAA+B;gBAEhC,OAAO,CAAC,EAAE,MAAM;CAI7B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAQ/B;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IACjD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,KAAK,GAAG,KAAK,IAC5C,UAAU,CAAC,CAAC,CAAC,GACb,SAAS,CAAC,CAAC,CAAC,GACZ,UAAU,CAAC,CAAC,CAAC,CAAC;AAElB,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAEhD;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAEhE"}
1
+ {"version":3,"file":"RunResult.d.ts","sourceRoot":"","sources":["../src/RunResult.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAE7D;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,qBAA+B;gBAEhC,OAAO,CAAC,EAAE,MAAM;CAI7B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAQ/B;AAED,QAAA,MAAM,cAAc,eAAwB,CAAC;AAE7C,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,IAAI;IAChD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,KAAK,GAAG,KAAK,IAC5C,UAAU,CAAC,CAAC,CAAC,GACb,SAAS,CAAC,CAAC,CAAC,GACZ,UAAU,CAAC,CAAC,CAAC,CAAC;AAElB,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAC3C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAOxC;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAEhD;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAEhE"}
package/dist/RunResult.js CHANGED
@@ -24,12 +24,19 @@ export function isAdaptiveTimeoutError(error) {
24
24
  "code" in error &&
25
25
  error.code === ADAPTIVE_TIMEOUT_ERROR_CODE));
26
26
  }
27
+ const isRunResultTag = Symbol("isRunResult");
28
+ export function isRunResult(value) {
29
+ return (typeof value === "object" &&
30
+ value !== null &&
31
+ isRunResultTag in value &&
32
+ value[isRunResultTag] === true);
33
+ }
27
34
  export function success(value) {
28
- return { kind: "success", value };
35
+ return { kind: "success", [isRunResultTag]: true, value };
29
36
  }
30
37
  export function ignore(value) {
31
- return { kind: "ignore", value };
38
+ return { kind: "ignore", [isRunResultTag]: true, value };
32
39
  }
33
40
  export function dropped(error) {
34
- return { kind: "dropped", error };
41
+ return { kind: "dropped", [isRunResultTag]: true, error };
35
42
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export type { AdaptiveLimit } from "./limit/StreamingLimit.js";
2
2
  export type { LimitAllotment } from "./LimitAllotment.js";
3
- export { Limiter, SemaphoreStrategy, withLimiter, type AcquireOptions, type AcquireResult, type AcquireStrategy, type AllotmentUnavailableStrategy, type LimitedFunction, type LimiterOptions, type LimiterState, type RunCallbackArgs, type MaybePromise, } from "./Limiter.js";
3
+ export { Limiter, withLimiter, type AcquireOptions, type AcquireResult, type AcquireStrategy, type AllotmentUnavailableStrategy, type LimitedFunction, type LimiterOptions, type LimiterState, type MaybePromise, type RunCallbackArgs, } from "./Limiter.js";
4
4
  export { ListenerSet } from "./ListenerSet.js";
5
5
  export { MetricIds, NoopMetricRegistry, type Counter, type DistributionMetric, type GaugeMetric, type MetricRegistry, } from "./MetricRegistry.js";
6
- export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, QuotaNotAvailable, success, type RunDropped, type RunIgnore, type RunResult, type RunSuccess, } from "./RunResult.js";
6
+ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunResult, QuotaNotAvailable, success, type RunDropped, type RunIgnore, type RunResult, type RunSuccess, } from "./RunResult.js";
7
7
  export { AIMDLimit, type AIMDLimitOptions } from "./limit/AIMDLimit.js";
8
8
  export { FixedLimit } from "./limit/FixedLimit.js";
9
9
  export { GradientLimit, type Gradient2LimitOptions, } from "./limit/GradientLimit.js";
@@ -20,6 +20,7 @@ export type { SampleWindow } from "./limit/window/SampleWindow.js";
20
20
  export { squareRoot, squareRootWithBaseline } from "./utils/index.js";
21
21
  export * from "./limiter/factories/index.js";
22
22
  export { PartitionedStrategy, type PartitionConfig, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
23
+ export { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
23
24
  export { DelayedRejectStrategy, type DelayedRejectStrategyOptions, } from "./limiter/allocation-unavailable-strategies/DelayedRejectStrategy.js";
24
25
  export { DelayedThenBlockingRejection } from "./limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js";
25
26
  export { FifoBlockingRejection } from "./limiter/allocation-unavailable-strategies/FifoBlockingRejection.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,iBAAiB,EACjB,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAGtE,cAAc,8BAA8B,CAAC;AAC7C,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,qDAAqD,CAAC;AAG7D,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6EAA6E,CAAC;AAC3H,OAAO,EAAE,qBAAqB,EAAE,MAAM,sEAAsE,CAAC;AAC7G,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,qDAAqD,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mDAAmD,CAAC;AAGtF,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6EAA6E,CAAC;AAC3H,OAAO,EAAE,qBAAqB,EAAE,MAAM,sEAAsE,CAAC;AAC7G,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- export { Limiter, SemaphoreStrategy, withLimiter, } from "./Limiter.js";
1
+ export { Limiter, withLimiter, } from "./Limiter.js";
2
2
  export { ListenerSet } from "./ListenerSet.js";
3
3
  export { MetricIds, NoopMetricRegistry, } from "./MetricRegistry.js";
4
- export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, QuotaNotAvailable, success, } from "./RunResult.js";
4
+ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunResult, QuotaNotAvailable, success, } from "./RunResult.js";
5
5
  // Limit algorithms
6
6
  export { AIMDLimit } from "./limit/AIMDLimit.js";
7
7
  export { FixedLimit } from "./limit/FixedLimit.js";
@@ -18,9 +18,10 @@ export { makeAverageSampleWindow } from "./limit/window/AverageSampleWindow.js";
18
18
  export { createPercentileSampleWindow } from "./limit/window/PercentileSampleWindow.js";
19
19
  // Limit functions
20
20
  export { squareRoot, squareRootWithBaseline } from "./utils/index.js";
21
- // Acquire strategies
22
21
  export * from "./limiter/factories/index.js";
22
+ // Acquire strategies
23
23
  export { PartitionedStrategy, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
24
+ export { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
24
25
  // Rejection strategies
25
26
  export { DelayedRejectStrategy, } from "./limiter/allocation-unavailable-strategies/DelayedRejectStrategy.js";
26
27
  export { DelayedThenBlockingRejection } from "./limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js";
@@ -6,6 +6,25 @@ export type PartitionConfig = {
6
6
  * [0.0, 1.0].
7
7
  */
8
8
  percent: number;
9
+ /**
10
+ * Controls whether this partition may exceed its limit-at-global-saturation
11
+ * while global inflight is still below the global limit.
12
+ *
13
+ * - `{ kind: "unbounded" }` (default): current behavior; any partition may
14
+ * consume all global slack.
15
+ * - `{ kind: "capped", maxBurstMultiplier }`: partition may burst, but only
16
+ * up to `ceil(limitAtGlobalSaturation * maxBurstMultiplier)`.
17
+ * - `{ kind: "none" }`: no bursting; partition cannot exceed its
18
+ * `limitAtGlobalSaturation`, even when global slack exists.
19
+ */
20
+ burstMode?: {
21
+ kind: "unbounded";
22
+ } | {
23
+ kind: "capped";
24
+ maxBurstMultiplier: number;
25
+ } | {
26
+ kind: "none";
27
+ } | undefined;
9
28
  };
10
29
  /**
11
30
  * ## Partitioned acquire strategy (guaranteed share + bursting)
@@ -16,9 +35,12 @@ export type PartitionConfig = {
16
35
  * max(1, ceil(globalLimit * percent))`.
17
36
  *
18
37
  * Admission policy:
19
- * - If `globalInflight < globalLimit`, the request is admitted regardless of
20
- * its partition's current limitAtGlobalSaturation usage (bursting while slack
21
- * exists).
38
+ * - If `globalInflight < globalLimit`, admission is controlled by partition
39
+ * burst policy:
40
+ * - `unbounded` (default): always admit while global slack exists.
41
+ * - `capped`: admit up to
42
+ * `ceil(limitAtGlobalSaturation * maxBurstMultiplier)`.
43
+ * - `none`: admit only up to `limitAtGlobalSaturation`.
22
44
  *
23
45
  * - If `globalInflight >= globalLimit`, admission is checked against the
24
46
  * resolved partition's limitAtGlobalSaturation only (`tryAcquire` on that
@@ -30,8 +52,7 @@ export type PartitionConfig = {
30
52
  *
31
53
  * Examples (global limit = 10, A=50%, B=50% => limitsAtGlobalSaturation: A=5,
32
54
  * B=5):
33
- * - **Bursting:** A can reach inflight 10 while B is 0 (below-saturation
34
- * admissions do not enforce per-partition limits at global saturation).
55
+ * - **Unbounded bursting:** A can reach inflight 10 while B is 0.
35
56
  *
36
57
  * - **Limit-at-global-saturation catch-up at saturation:** if A=10 and B=0,
37
58
  * then B requests can still be admitted up to B=5 via `tryAcquire`, so global
@@ -81,6 +102,14 @@ export declare class PartitionedStrategy<ContextT, PartitionName extends string
81
102
  getPartition(name: PartitionName): {
82
103
  name: PartitionName;
83
104
  percent: number;
105
+ burstMode: {
106
+ kind: "unbounded";
107
+ } | {
108
+ kind: "capped";
109
+ maxBurstMultiplier: number;
110
+ } | {
111
+ kind: "none";
112
+ };
84
113
  limitAtGlobalSaturation: number;
85
114
  inFlight: number;
86
115
  isLimitAtGlobalSaturationExceeded: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"PartitionedStrategy.d.ts","sourceRoot":"","sources":["../../../src/limiter/acquire-strategies/PartitionedStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,yBAAyB,CAAC;AAKjC,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,mBAAmB,CAC9B,QAAQ,EACR,aAAa,SAAS,MAAM,GAAG,MAAM,CACrC,YAAW,eAAe,CAAC,QAAQ,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG9B;IACF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAEH;gBAEnB,OAAO,EAAE;QACnB;;;WAGG;QACH,YAAY,EAAE,MAAM,CAAC;QAErB;;;;;WAKG;QACH,iBAAiB,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,GAAG,SAAS,CAAC;QACpE,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;QAEnD,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;KAC7C;IAkDD,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO;IAUpE,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI;IAI5C,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMzD;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,aAAa;;;;;;;IAehC,OAAO,CAAC,gBAAgB;CAUzB"}
1
+ {"version":3,"file":"PartitionedStrategy.d.ts","sourceRoot":"","sources":["../../../src/limiter/acquire-strategies/PartitionedStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,yBAAyB,CAAC;AAKjC,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EACN;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,GACrB;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,GAC9C;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB,SAAS,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,qBAAa,mBAAmB,CAC9B,QAAQ,EACR,aAAa,SAAS,MAAM,GAAG,MAAM,CACrC,YAAW,eAAe,CAAC,QAAQ,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG9B;IACF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAEH;gBAEnB,OAAO,EAAE;QACnB;;;WAGG;QACH,YAAY,EAAE,MAAM,CAAC;QAErB;;;;;WAKG;QACH,iBAAiB,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,GAAG,SAAS,CAAC;QACpE,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;QAEnD,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;KAC7C;IAmDD,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO;IASpE,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI;IAI5C,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMzD;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,aAAa;;;;kBAuCpB,WAAW;;kBACX,QAAQ;gCAAsB,MAAM;;kBACpC,MAAM;;;;;;IAzBlB,OAAO,CAAC,gBAAgB;CAUzB"}
@@ -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
+ }