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.
- package/dist/LimitAllotment.d.ts +7 -1
- package/dist/LimitAllotment.d.ts.map +1 -1
- package/dist/Limiter.d.ts +14 -21
- package/dist/Limiter.d.ts.map +1 -1
- package/dist/Limiter.js +31 -47
- package/dist/RunResult.d.ts +12 -6
- package/dist/RunResult.d.ts.map +1 -1
- package/dist/RunResult.js +10 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/limiter/acquire-strategies/PartitionedStrategy.d.ts +34 -5
- package/dist/limiter/acquire-strategies/PartitionedStrategy.d.ts.map +1 -1
- package/dist/limiter/acquire-strategies/PartitionedStrategy.js +34 -9
- package/dist/limiter/acquire-strategies/SemaphoreStrategy.d.ts +13 -0
- package/dist/limiter/acquire-strategies/SemaphoreStrategy.d.ts.map +1 -0
- package/dist/limiter/acquire-strategies/SemaphoreStrategy.js +23 -0
- package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.d.ts +51 -0
- package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.d.ts.map +1 -0
- package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.js +176 -0
- package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts +1 -1
- package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts.map +1 -1
- package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js +6 -0
- package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.d.ts +19 -15
- package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.d.ts.map +1 -1
- package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.js +12 -61
- package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.d.ts +4 -8
- package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.d.ts.map +1 -1
- package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.js +12 -49
- package/dist/limiter/factories/makeBlockingLimiter.d.ts +3 -1
- package/dist/limiter/factories/makeBlockingLimiter.d.ts.map +1 -1
- package/dist/limiter/factories/makeBlockingLimiter.js +3 -2
- package/dist/limiter/factories/makeLifoBlockingLimiter.d.ts +1 -1
- package/dist/limiter/factories/makeLifoBlockingLimiter.d.ts.map +1 -1
- package/dist/limiter/factories/makeLifoBlockingLimiter.js +1 -1
- package/dist/limiter/factories/makePartitionedBlockingLimiter.d.ts +3 -1
- package/dist/limiter/factories/makePartitionedBlockingLimiter.d.ts.map +1 -1
- package/dist/limiter/factories/makePartitionedBlockingLimiter.js +5 -4
- package/dist/limiter/factories/makeSimpleLimiter.d.ts.map +1 -1
- package/dist/limiter/factories/makeSimpleLimiter.js +2 -1
- package/package.json +3 -2
package/dist/LimitAllotment.d.ts
CHANGED
|
@@ -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-
|
|
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
|
|
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):
|
|
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}.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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.
|
package/dist/Limiter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
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
|
|
192
|
+
return outcomeCast.value;
|
|
209
193
|
case "ignore":
|
|
210
194
|
await allotment.releaseAndIgnore();
|
|
211
|
-
return
|
|
195
|
+
return outcomeCast.value;
|
|
212
196
|
case "dropped":
|
|
213
197
|
await allotment.releaseAndRecordDropped();
|
|
214
|
-
throw
|
|
198
|
+
throw outcomeCast.error;
|
|
215
199
|
}
|
|
216
200
|
}
|
|
217
201
|
return limited;
|
package/dist/RunResult.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
22
|
+
};
|
|
23
|
+
export type RunIgnore<T> = {
|
|
22
24
|
readonly kind: "ignore";
|
|
25
|
+
readonly [isRunResultTag]: true;
|
|
23
26
|
readonly value: T;
|
|
24
|
-
}
|
|
25
|
-
export
|
|
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
|
package/dist/RunResult.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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`,
|
|
20
|
-
*
|
|
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
|
-
* - **
|
|
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;
|
|
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`,
|
|
13
|
-
*
|
|
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
|
-
* - **
|
|
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.
|
|
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
|
+
}
|