dynamic-throttled-queue 2.3.0-rc.494c954 → 2.3.0-rc.63bc2b5
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/README.md +26 -2
- package/dist/adaptive-rate.d.ts +36 -6
- package/dist/adaptive-rate.js +35 -9
- package/dist/adaptive-rate.js.map +1 -1
- package/dist/dynamic-throttled-queue.d.ts +5 -59
- package/dist/dynamic-throttled-queue.js +13 -32
- package/dist/dynamic-throttled-queue.js.map +1 -1
- package/dist/pending-work.d.ts +2 -0
- package/dist/pending-work.js +31 -13
- package/dist/pending-work.js.map +1 -1
- package/dist/retry-policy.d.ts +1 -1
- package/dist/scheduler.d.ts +51 -2
- package/dist/scheduler.js +96 -26
- package/dist/scheduler.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ throttle(() => {
|
|
|
28
28
|
|
|
29
29
|
Callbacks can return `false` to signal a failure (used for dynamic rate adjustment and retry). Async callbacks (returning a Promise) are also supported — rejections and `false` resolutions count as failures. By default, every failure reduces the adaptive rate; use `rateOutcomeClassifier` when only selected failures should do so. Use `retryClassifier` to separately decide whether a failure is eligible for retry.
|
|
30
30
|
|
|
31
|
-
Each callback receives an execution context containing the queue-owned `
|
|
31
|
+
Each callback receives an execution context containing an `AbortSignal` and the one-based `attempt` number (retries count up from 1). Fire-and-forget callbacks get the queue-owned signal, which only `abort()` aborts. Callbacks passed to [`submit()`](#task-handles) get a signal scoped to their item instead. Existing zero-argument callbacks remain supported. Use the signal to cooperatively cancel in-flight work:
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
34
|
throttle(async ({ signal }) => {
|
|
@@ -41,6 +41,29 @@ Set `concurrency` to bound callbacks that are still awaiting asynchronous comple
|
|
|
41
41
|
|
|
42
42
|
Set `maxQueueSize` to bound accepted work. Capacity is reserved from enqueue through terminal success or failure, including pending callbacks, active callbacks, and retries. A full queue makes `enqueue()` throw synchronously in v2; unlimited capacity remains the default. In v3, a full queue will return `false` instead.
|
|
43
43
|
|
|
44
|
+
### Task handles
|
|
45
|
+
|
|
46
|
+
`submit()` accepts a callback like the fire-and-forget form, but returns a handle for that one item, covering every attempt it makes:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const task = queue.submit(async ({ signal, attempt }) => {
|
|
50
|
+
const response = await fetch("/api/data", { signal });
|
|
51
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} on attempt ${attempt}`);
|
|
52
|
+
return response.json();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
task.cancel();
|
|
56
|
+
const data = await task.result;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- **Results.** Every value the callback returns or resolves, including `false`, is the result. Only a throw or a rejection is a failure, and it goes through `retry`, `retryClassifier`, and `rateOutcomeClassifier` the same as any other failure. `result` fulfills once, with the final successful value, or rejects once, with the final attempt's error after retries end.
|
|
60
|
+
- **Cancellation.** `cancel(reason?)` rejects `result` immediately with `reason`, or with an `AbortError` when no reason is given. A canceled item that has not started (including one waiting on a delayed retry) is removed: it never starts, frees its `maxQueueSize` slot, and does not use a start slot. A canceled active item has its own signal aborted, and only that signal; other work and the queue signal are unaffected. It keeps its concurrency slot until the callback returns, and `waitForIdle()` waits for it. Its outcome never retries and never counts as a success, a failure, or a rate-reducing error. Calling `cancel()` again, or after `result` has settled, does nothing.
|
|
61
|
+
- **Queue termination.** `abort()` rejects every unsettled item at once with the queue signal's `AbortError` and aborts each item's signal. A strategy failure (see [Lifecycle](#lifecycle)) does the same with the strategy error. Values that arrive later are discarded.
|
|
62
|
+
- **Retained work.** `pause()` and `stop()` keep items and leave their results pending.
|
|
63
|
+
- **Admission.** `submit()` throws synchronously and returns no handle when `enqueue()` would throw: the queue is full, aborted, or failed.
|
|
64
|
+
|
|
65
|
+
`result` is an ordinary promise. If you cancel or abort without awaiting it, attach a handler (for example `task.result.catch(() => {})`) so the rejection is not reported as unhandled.
|
|
66
|
+
|
|
44
67
|
## Options
|
|
45
68
|
|
|
46
69
|
| Param | Type | Default | Description |
|
|
@@ -78,6 +101,7 @@ With `adjustmentTiming: "settled"`, an observation window contains every callbac
|
|
|
78
101
|
|
|
79
102
|
| Property | Type | Description |
|
|
80
103
|
| -------- | ---- | ----------- |
|
|
104
|
+
| `submit(callback)` | `<T>(callback) => TaskHandle<T>` | Enqueue one item and return its `{ result, cancel }` handle; see [Task handles](#task-handles) |
|
|
81
105
|
| `pause()` | `() => void` | Temporarily prevent new callback starts while retaining accepted work |
|
|
82
106
|
| `resume()` | `() => void` | Resume a paused queue with a fresh pacing and observation window |
|
|
83
107
|
| `stop()` | `() => void` | Stop processing the queue immediately |
|
|
@@ -115,7 +139,7 @@ In settled timing, `pause()` discards the in-progress observation window rather
|
|
|
115
139
|
|
|
116
140
|
A queue fails when `rateStrategy` throws or returns a malformed decision. The queue clears its timers, discards pending callbacks and delayed retries, and reports `state: "failed"`. The original error is still rethrown where the decision ran: an uncaught exception from the adaptive-rate timer, or an unhandled rejection when a slow asynchronous callback completes a settled-timing window. Pending `waitForIdle()` promises reject with that error immediately, even while callbacks are still active; later `waitForIdle()` calls reject with it and later enqueues throw it. Unlike `abort()`, failure does not wait for active callbacks to settle. Create a new queue to resume work.
|
|
117
141
|
|
|
118
|
-
`getState()` returns a frozen `QueueState` snapshot. Each call is independent; mutations to the returned object do not affect the queue. Fields: `rate` (current rate), `pending` (queued + delayed retries), `active` (callbacks executing), `state` (`"running"` | `"paused"` | `"stopped"` | `"aborted"` | `"failed"`), and monotonic lifetime counters `started`, `succeeded`, `failed`, `retried`, `rateIncreases`, `rateDecreases`. `started`/`succeeded`/`failed` count callback attempts: a retry is a new attempt; a failure is counted even when a later retry succeeds. `retried` increments only when another attempt is actually scheduled. Rate-direction counters match applied rate changes and `onRateChange` notifications exactly. Counters do not change for settlements after `abort()` or a strategy failure.
|
|
142
|
+
`getState()` returns a frozen `QueueState` snapshot. Each call is independent; mutations to the returned object do not affect the queue. Fields: `rate` (current rate), `pending` (queued + delayed retries), `active` (callbacks executing), `state` (`"running"` | `"paused"` | `"stopped"` | `"aborted"` | `"failed"`), and monotonic lifetime counters `started`, `succeeded`, `failed`, `retried`, `canceled`, `rateIncreases`, `rateDecreases`. `started`/`succeeded`/`failed` count callback attempts: a retry is a new attempt; a failure is counted even when a later retry succeeds. `retried` increments only when another attempt is actually scheduled. `canceled` counts submitted items whose result `cancel()` rejected; a canceled active attempt counts as neither succeeded nor failed. Rate-direction counters match applied rate changes and `onRateChange` notifications exactly. Counters do not change for settlements after `abort()` or a strategy failure.
|
|
119
143
|
|
|
120
144
|
`waitForIdle()` returns a `Promise<void>` that resolves once all pending callbacks, active executions, and delayed retries have reached a terminal outcome. If the queue is already idle the promise resolves immediately. Each call is one-shot: a later enqueue does not affect a promise that has already resolved. Multiple simultaneous callers all resolve at the same idle transition without retaining waiter state. A callback failure that triggers a retry never exposes a transient idle transition between the failed attempt and the retry. `stop()` retains pending work, so existing waiters remain pending until that work completes after a later enqueue resumes the queue. `abort()` discards pending work, so waiters resolve as soon as no callback is active: immediately when none is running, otherwise when the last active callback settles. Post-abort settlements do not create new retry work. A paused queue resolves waiters only when both pending and active work are zero. A failed queue rejects every waiter with the strategy error at once, without waiting for active callbacks.
|
|
121
145
|
|
package/dist/adaptive-rate.d.ts
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
export type RateStrategyObservation = Readonly<{
|
|
2
|
+
currentRate: number;
|
|
3
|
+
minRate: number;
|
|
4
|
+
maxRate: number;
|
|
5
|
+
errorCount: number;
|
|
6
|
+
errorThreshold: number;
|
|
7
|
+
hasPendingWork: boolean;
|
|
8
|
+
wasBackedOff: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
export type RateStrategyDecision = Readonly<{
|
|
11
|
+
nextRate: number;
|
|
12
|
+
shouldBackOff: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
export type RateStrategy = (observation: RateStrategyObservation) => RateStrategyDecision;
|
|
15
|
+
export type AimdOptions = {
|
|
16
|
+
increaseBy?: number;
|
|
17
|
+
decreaseFactor?: number;
|
|
18
|
+
};
|
|
19
|
+
export type RateFailureOutcome = Readonly<{
|
|
20
|
+
kind: "returned-false";
|
|
21
|
+
}> | Readonly<{
|
|
22
|
+
kind: "thrown";
|
|
23
|
+
error: unknown;
|
|
24
|
+
}> | Readonly<{
|
|
25
|
+
kind: "rejected";
|
|
26
|
+
error: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
export type RateOutcomeClassifier = (outcome: RateFailureOutcome) => boolean;
|
|
29
|
+
export type AdjustmentTiming = "interval" | "settled";
|
|
30
|
+
export declare const linear: RateStrategy;
|
|
31
|
+
export declare function aimd({ increaseBy, decreaseFactor }?: AimdOptions): RateStrategy;
|
|
2
32
|
/** Reports how one started callback settled: `undefined` for success, otherwise the failure. */
|
|
3
33
|
export type SettlementReporter = (outcome: RateFailureOutcome | undefined) => void;
|
|
4
34
|
export type AdaptiveRateOptions = {
|
|
5
|
-
|
|
6
|
-
|
|
35
|
+
minRate: number;
|
|
36
|
+
maxRate: number;
|
|
7
37
|
interval: number;
|
|
8
38
|
/** Makes one start due every `interval / rate` ms instead of `rate` starts once per interval. */
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
39
|
+
evenlySpaced: boolean;
|
|
40
|
+
errorThreshold: number;
|
|
41
|
+
backOff: boolean;
|
|
12
42
|
adjustmentTiming: AdjustmentTiming;
|
|
13
43
|
rateStrategy: RateStrategy;
|
|
14
44
|
rateOutcomeClassifier?: RateOutcomeClassifier;
|
package/dist/adaptive-rate.js
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
export const linear = ({ minRate, maxRate, currentRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff, }) => {
|
|
2
|
+
if (errorCount >= errorThreshold) {
|
|
3
|
+
return { nextRate: Math.max(minRate, currentRate - 1), shouldBackOff: true };
|
|
4
|
+
}
|
|
5
|
+
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
6
|
+
return { nextRate: Math.min(maxRate, currentRate + 1), shouldBackOff: false };
|
|
7
|
+
}
|
|
8
|
+
return { nextRate: currentRate, shouldBackOff: false };
|
|
9
|
+
};
|
|
10
|
+
export function aimd({ increaseBy = 1, decreaseFactor = 0.5 } = {}) {
|
|
11
|
+
if (!Number.isInteger(increaseBy) || increaseBy < 1) {
|
|
12
|
+
throw new Error("increaseBy must be a positive integer");
|
|
13
|
+
}
|
|
14
|
+
if (!Number.isFinite(decreaseFactor) || decreaseFactor <= 0 || decreaseFactor >= 1) {
|
|
15
|
+
throw new Error("decreaseFactor must be a number greater than 0 and less than 1");
|
|
16
|
+
}
|
|
17
|
+
return ({ currentRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff }) => {
|
|
18
|
+
if (errorCount >= errorThreshold) {
|
|
19
|
+
return { nextRate: Math.floor(currentRate * decreaseFactor), shouldBackOff: true };
|
|
20
|
+
}
|
|
21
|
+
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
22
|
+
return { nextRate: currentRate + increaseBy, shouldBackOff: false };
|
|
23
|
+
}
|
|
24
|
+
return { nextRate: currentRate, shouldBackOff: false };
|
|
25
|
+
};
|
|
26
|
+
}
|
|
1
27
|
const ignoreSettlement = () => { };
|
|
2
28
|
function validateDecision(decision) {
|
|
3
29
|
if (typeof decision !== "object" || decision === null) {
|
|
@@ -114,8 +140,8 @@ function settledTiming(context) {
|
|
|
114
140
|
};
|
|
115
141
|
}
|
|
116
142
|
export function createAdaptiveRate(options, host) {
|
|
117
|
-
const {
|
|
118
|
-
let rate = Math.ceil((
|
|
143
|
+
const { minRate, maxRate, interval, evenlySpaced, errorThreshold, backOff, rateStrategy, rateOutcomeClassifier, onRateChange, } = options;
|
|
144
|
+
let rate = Math.ceil((maxRate + minRate) / 2);
|
|
119
145
|
let rateIncreases = 0;
|
|
120
146
|
let rateDecreases = 0;
|
|
121
147
|
let errorCount = 0;
|
|
@@ -147,10 +173,10 @@ export function createAdaptiveRate(options, host) {
|
|
|
147
173
|
function decide() {
|
|
148
174
|
const observation = Object.freeze({
|
|
149
175
|
currentRate: rate,
|
|
150
|
-
minRate
|
|
151
|
-
maxRate
|
|
176
|
+
minRate,
|
|
177
|
+
maxRate,
|
|
152
178
|
errorCount,
|
|
153
|
-
errorThreshold
|
|
179
|
+
errorThreshold,
|
|
154
180
|
hasPendingWork: host.hasPendingWork(),
|
|
155
181
|
wasBackedOff,
|
|
156
182
|
});
|
|
@@ -162,14 +188,14 @@ export function createAdaptiveRate(options, host) {
|
|
|
162
188
|
host.failed(error);
|
|
163
189
|
throw error;
|
|
164
190
|
}
|
|
165
|
-
const hold =
|
|
191
|
+
const hold = backOff && decision.shouldBackOff;
|
|
166
192
|
errorCount = 0;
|
|
167
193
|
wasBackedOff = hold;
|
|
168
|
-
applyRate(Math.min(
|
|
194
|
+
applyRate(Math.min(maxRate, Math.max(minRate, decision.nextRate)));
|
|
169
195
|
return hold;
|
|
170
196
|
}
|
|
171
197
|
function spacing() {
|
|
172
|
-
return
|
|
198
|
+
return evenlySpaced ? interval / rate : interval;
|
|
173
199
|
}
|
|
174
200
|
function scheduleStart(delay) {
|
|
175
201
|
clearTimeout(startTimer);
|
|
@@ -191,7 +217,7 @@ export function createAdaptiveRate(options, host) {
|
|
|
191
217
|
batchStarts = 0;
|
|
192
218
|
let slotFree;
|
|
193
219
|
try {
|
|
194
|
-
slotFree = host.startsDue(
|
|
220
|
+
slotFree = host.startsDue(evenlySpaced ? 1 : rate);
|
|
195
221
|
}
|
|
196
222
|
finally {
|
|
197
223
|
dispatching = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-rate.js","sourceRoot":"","sources":["../src/adaptive-rate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adaptive-rate.js","sourceRoot":"","sources":["../src/adaptive-rate.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,41 +1,10 @@
|
|
|
1
|
+
import type { AdjustmentTiming, RateOutcomeClassifier, RateStrategy } from "./adaptive-rate.js";
|
|
1
2
|
import type { RetryBackoff, RetryClassifier } from "./retry-policy.js";
|
|
3
|
+
import type { ThrottleHandle } from "./scheduler.js";
|
|
4
|
+
export { aimd, linear } from "./adaptive-rate.js";
|
|
5
|
+
export type { AdjustmentTiming, AimdOptions, RateFailureOutcome, RateOutcomeClassifier, RateStrategy, RateStrategyDecision, RateStrategyObservation } from "./adaptive-rate.js";
|
|
2
6
|
export type { RetryBackoff, RetryClassifier } from "./retry-policy.js";
|
|
3
|
-
export type
|
|
4
|
-
currentRate: number;
|
|
5
|
-
minRate: number;
|
|
6
|
-
maxRate: number;
|
|
7
|
-
errorCount: number;
|
|
8
|
-
errorThreshold: number;
|
|
9
|
-
hasPendingWork: boolean;
|
|
10
|
-
wasBackedOff: boolean;
|
|
11
|
-
}>;
|
|
12
|
-
export type RateStrategyDecision = Readonly<{
|
|
13
|
-
nextRate: number;
|
|
14
|
-
shouldBackOff: boolean;
|
|
15
|
-
}>;
|
|
16
|
-
export type RateStrategy = (observation: RateStrategyObservation) => RateStrategyDecision;
|
|
17
|
-
export type AimdOptions = {
|
|
18
|
-
increaseBy?: number;
|
|
19
|
-
decreaseFactor?: number;
|
|
20
|
-
};
|
|
21
|
-
export type RateFailureOutcome = Readonly<{
|
|
22
|
-
kind: "returned-false";
|
|
23
|
-
}> | Readonly<{
|
|
24
|
-
kind: "thrown";
|
|
25
|
-
error: unknown;
|
|
26
|
-
}> | Readonly<{
|
|
27
|
-
kind: "rejected";
|
|
28
|
-
error: unknown;
|
|
29
|
-
}>;
|
|
30
|
-
export type RateOutcomeClassifier = (outcome: RateFailureOutcome) => boolean;
|
|
31
|
-
export type AdjustmentTiming = "interval" | "settled";
|
|
32
|
-
export declare const linear: RateStrategy;
|
|
33
|
-
export declare function aimd({ increaseBy, decreaseFactor }?: AimdOptions): RateStrategy;
|
|
34
|
-
export type ExecutionContext = Readonly<{
|
|
35
|
-
signal: AbortSignal;
|
|
36
|
-
}>;
|
|
37
|
-
/** Return `false` to signal failure (increments error count, triggers retry if configured). */
|
|
38
|
-
export type ThrottleCallback = (context: ExecutionContext) => boolean | void | Promise<boolean | void>;
|
|
7
|
+
export type { ExecutionContext, QueueLifecycleState, QueueState, TaskCallback, TaskHandle, ThrottleCallback, ThrottleFn, ThrottleHandle } from "./scheduler.js";
|
|
39
8
|
export type ThrottleOptions = {
|
|
40
9
|
min_rpi: number;
|
|
41
10
|
interval: number;
|
|
@@ -64,27 +33,4 @@ export type ThrottleOptions = {
|
|
|
64
33
|
adjustmentTiming?: AdjustmentTiming;
|
|
65
34
|
onRateChange?: (rate: number) => void;
|
|
66
35
|
};
|
|
67
|
-
export type ThrottleFn = (callback: ThrottleCallback) => void;
|
|
68
|
-
export type QueueLifecycleState = "running" | "paused" | "stopped" | "aborted" | "failed";
|
|
69
|
-
export type QueueState = Readonly<{
|
|
70
|
-
rate: number;
|
|
71
|
-
pending: number;
|
|
72
|
-
active: number;
|
|
73
|
-
state: QueueLifecycleState;
|
|
74
|
-
started: number;
|
|
75
|
-
succeeded: number;
|
|
76
|
-
failed: number;
|
|
77
|
-
retried: number;
|
|
78
|
-
rateIncreases: number;
|
|
79
|
-
rateDecreases: number;
|
|
80
|
-
}>;
|
|
81
|
-
export type ThrottleHandle = ThrottleFn & {
|
|
82
|
-
pause: () => void;
|
|
83
|
-
resume: () => void;
|
|
84
|
-
stop: () => void;
|
|
85
|
-
abort: () => void;
|
|
86
|
-
waitForIdle: () => Promise<void>;
|
|
87
|
-
getState: () => QueueState;
|
|
88
|
-
readonly pending: number;
|
|
89
|
-
};
|
|
90
36
|
export declare function createThrottledQueue(options: ThrottleOptions): ThrottleHandle;
|
|
@@ -1,32 +1,9 @@
|
|
|
1
|
+
import { linear } from "./adaptive-rate.js";
|
|
1
2
|
import { createRetryPolicy } from "./retry-policy.js";
|
|
2
3
|
import { createScheduler } from "./scheduler.js";
|
|
4
|
+
// eslint-disable-next-line no-barrel-files/no-barrel-files -- The package entry exposes the rate strategies owned by the adaptive-rate module.
|
|
5
|
+
export { aimd, linear } from "./adaptive-rate.js";
|
|
3
6
|
const adjustmentTimings = new Set(["interval", "settled"]);
|
|
4
|
-
export const linear = ({ minRate, maxRate, currentRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff, }) => {
|
|
5
|
-
if (errorCount >= errorThreshold) {
|
|
6
|
-
return { nextRate: Math.max(minRate, currentRate - 1), shouldBackOff: true };
|
|
7
|
-
}
|
|
8
|
-
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
9
|
-
return { nextRate: Math.min(maxRate, currentRate + 1), shouldBackOff: false };
|
|
10
|
-
}
|
|
11
|
-
return { nextRate: currentRate, shouldBackOff: false };
|
|
12
|
-
};
|
|
13
|
-
export function aimd({ increaseBy = 1, decreaseFactor = 0.5 } = {}) {
|
|
14
|
-
if (!Number.isInteger(increaseBy) || increaseBy < 1) {
|
|
15
|
-
throw new Error("increaseBy must be a positive integer");
|
|
16
|
-
}
|
|
17
|
-
if (!Number.isFinite(decreaseFactor) || decreaseFactor <= 0 || decreaseFactor >= 1) {
|
|
18
|
-
throw new Error("decreaseFactor must be a number greater than 0 and less than 1");
|
|
19
|
-
}
|
|
20
|
-
return ({ currentRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff }) => {
|
|
21
|
-
if (errorCount >= errorThreshold) {
|
|
22
|
-
return { nextRate: Math.floor(currentRate * decreaseFactor), shouldBackOff: true };
|
|
23
|
-
}
|
|
24
|
-
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
25
|
-
return { nextRate: currentRate + increaseBy, shouldBackOff: false };
|
|
26
|
-
}
|
|
27
|
-
return { nextRate: currentRate, shouldBackOff: false };
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
7
|
export function createThrottledQueue(options) {
|
|
31
8
|
const { min_rpi, interval, max_rpi = min_rpi, concurrency, maxQueueSize, compact_threshold = 512 } = options;
|
|
32
9
|
const errors_per_interval = options.errors_per_interval ?? 5;
|
|
@@ -55,13 +32,17 @@ export function createThrottledQueue(options) {
|
|
|
55
32
|
throw new Error("adjustmentTiming must be either interval or settled");
|
|
56
33
|
}
|
|
57
34
|
const retryPolicy = createRetryPolicy(options);
|
|
58
|
-
return createScheduler(
|
|
59
|
-
|
|
60
|
-
|
|
35
|
+
return createScheduler({
|
|
36
|
+
concurrency: concurrency ?? Infinity,
|
|
37
|
+
capacity: maxQueueSize ?? Infinity,
|
|
38
|
+
compactThreshold: compact_threshold,
|
|
39
|
+
}, {
|
|
40
|
+
minRate: min_rpi,
|
|
41
|
+
maxRate: max_rpi,
|
|
61
42
|
interval,
|
|
62
|
-
|
|
63
|
-
errors_per_interval,
|
|
64
|
-
|
|
43
|
+
evenlySpaced: options.evenly_spaced ?? true,
|
|
44
|
+
errorThreshold: errors_per_interval,
|
|
45
|
+
backOff: options.back_off ?? false,
|
|
65
46
|
adjustmentTiming: options.adjustmentTiming ?? "interval",
|
|
66
47
|
rateStrategy: options.rateStrategy ?? linear,
|
|
67
48
|
rateOutcomeClassifier: options.rateOutcomeClassifier,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,2BAA2B;AAE5C,OAAO,EAAE,iBAAiB,EAAE,0BAA0B;AAEtD,OAAO,EAAE,eAAe,EAAE,uBAAuB;AAGjD,+IAA+I;AAC/I,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,2BAA2B;AAQlD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS,CAAE,UAAU,EAAE,SAAS,CAAE,CAAC,CAAC;AA+BrE,MAAM,UAAU,oBAAoB,CAAC,OAAwB;IAC3D,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,iBAAiB,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC;IAE7G,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,CAAC,CAAC;IAE7D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC;QACrF,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,mBAAmB,GAAG,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC/F,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,eAAe,CAAC;QACrB,WAAW,EAAE,WAAW,IAAI,QAAQ;QACpC,QAAQ,EAAE,YAAY,IAAI,QAAQ;QAClC,gBAAgB,EAAE,iBAAiB;KACpC,EAAE;QACD,OAAO,EAAE,OAAO;QAChB,OAAO,EAAE,OAAO;QAChB,QAAQ;QACR,YAAY,EAAE,OAAO,CAAC,aAAa,IAAI,IAAI;QAC3C,cAAc,EAAE,mBAAmB;QACnC,OAAO,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;QAClC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,UAAU;QACxD,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,MAAM;QAC5C,qBAAqB,EAAE,OAAO,CAAC,qBAAqB;QACpD,YAAY,EAAE,OAAO,CAAC,YAAY;KACnC,EAAE,WAAW,CAAC,CAAC;AAClB,CAAC"}
|
package/dist/pending-work.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export type PendingWork<T> = {
|
|
|
29
29
|
accept: (item: T) => void;
|
|
30
30
|
/** Makes the oldest queued item active and returns it, or `undefined` when nothing is queued. */
|
|
31
31
|
take: () => T | undefined;
|
|
32
|
+
/** Drops `item`, which must be queued or held, and releases its reservation. */
|
|
33
|
+
remove: (item: T) => void;
|
|
32
34
|
/**
|
|
33
35
|
* Ends one active item. `decide` runs once the item no longer counts as active but still holds its
|
|
34
36
|
* reservation; returning a retry keeps that reservation, returning nothing releases it. `observe`
|
package/dist/pending-work.js
CHANGED
|
@@ -2,6 +2,8 @@ export function createPendingWork(options, host) {
|
|
|
2
2
|
const { capacity, compactThreshold } = options;
|
|
3
3
|
const queue = [];
|
|
4
4
|
let head = 0;
|
|
5
|
+
/** Queued items that were removed but still sit between `head` and the tail; `take` skips them. */
|
|
6
|
+
const removed = new Set();
|
|
5
7
|
const held = [];
|
|
6
8
|
let frozen = false;
|
|
7
9
|
let active = 0;
|
|
@@ -9,7 +11,7 @@ export function createPendingWork(options, host) {
|
|
|
9
11
|
let deciding = 0;
|
|
10
12
|
const idleWaiters = [];
|
|
11
13
|
function queued() {
|
|
12
|
-
return queue.length - head;
|
|
14
|
+
return queue.length - head - removed.size;
|
|
13
15
|
}
|
|
14
16
|
function pending() {
|
|
15
17
|
return queued() + held.length;
|
|
@@ -44,6 +46,17 @@ export function createPendingWork(options, host) {
|
|
|
44
46
|
held.length = 0;
|
|
45
47
|
queue.length = 0;
|
|
46
48
|
head = 0;
|
|
49
|
+
removed.clear();
|
|
50
|
+
}
|
|
51
|
+
function compact() {
|
|
52
|
+
if (head === queue.length) {
|
|
53
|
+
queue.length = 0;
|
|
54
|
+
head = 0;
|
|
55
|
+
}
|
|
56
|
+
else if (head > compactThreshold && head > queue.length / 2) {
|
|
57
|
+
queue.splice(0, head);
|
|
58
|
+
head = 0;
|
|
59
|
+
}
|
|
47
60
|
}
|
|
48
61
|
return {
|
|
49
62
|
get queued() {
|
|
@@ -61,19 +74,24 @@ export function createPendingWork(options, host) {
|
|
|
61
74
|
queue.push(item);
|
|
62
75
|
},
|
|
63
76
|
take() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
else if (head > compactThreshold && head > queue.length / 2) {
|
|
73
|
-
queue.splice(0, head);
|
|
74
|
-
head = 0;
|
|
77
|
+
while (head < queue.length) {
|
|
78
|
+
const item = queue[head++];
|
|
79
|
+
if (removed.delete(item))
|
|
80
|
+
continue;
|
|
81
|
+
active++;
|
|
82
|
+
compact();
|
|
83
|
+
return item;
|
|
75
84
|
}
|
|
76
|
-
|
|
85
|
+
compact();
|
|
86
|
+
return undefined;
|
|
87
|
+
},
|
|
88
|
+
remove(item) {
|
|
89
|
+
const index = held.findIndex(retry => retry.item === item);
|
|
90
|
+
if (index === -1)
|
|
91
|
+
removed.add(item);
|
|
92
|
+
else
|
|
93
|
+
clearTimeout(held.splice(index, 1)[0].timeout);
|
|
94
|
+
notifyIdle();
|
|
77
95
|
},
|
|
78
96
|
settle(decide, observe) {
|
|
79
97
|
active--;
|
package/dist/pending-work.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending-work.js","sourceRoot":"","sources":["../src/pending-work.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pending-work.js","sourceRoot":"","sources":["../src/pending-work.ts"],"names":[],"mappings":"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"}
|
package/dist/retry-policy.d.ts
CHANGED
package/dist/scheduler.d.ts
CHANGED
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
import type { AdaptiveRateOptions } from "./adaptive-rate.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PendingWorkOptions } from "./pending-work.js";
|
|
3
3
|
import type { RetryPolicy } from "./retry-policy.js";
|
|
4
|
-
export
|
|
4
|
+
export type ExecutionContext = Readonly<{
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
/** One-based attempt number; retries of the same item count up from 1. */
|
|
7
|
+
attempt: number;
|
|
8
|
+
}>;
|
|
9
|
+
/** Return `false` to signal failure (increments error count, triggers retry if configured). */
|
|
10
|
+
export type ThrottleCallback = (context: ExecutionContext) => boolean | void | Promise<boolean | void>;
|
|
11
|
+
export type ThrottleFn = (callback: ThrottleCallback) => void;
|
|
12
|
+
/** Submitted work. Every returned value, including `false`, is a result; only a throw or rejection fails. */
|
|
13
|
+
export type TaskCallback<T> = (context: ExecutionContext) => T | Promise<T>;
|
|
14
|
+
/** One accepted logical item across all of its attempts. */
|
|
15
|
+
export type TaskHandle<T> = Readonly<{
|
|
16
|
+
/** Settles once: the final value, the final failure, or the cancellation reason. */
|
|
17
|
+
result: Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Rejects `result` with `reason` (an `AbortError` by default) unless it already settled. A pending
|
|
20
|
+
* item is removed; an active one has its signal aborted and is never retried. Repeat calls do nothing.
|
|
21
|
+
*/
|
|
22
|
+
cancel: (reason?: unknown) => void;
|
|
23
|
+
}>;
|
|
24
|
+
export type QueueLifecycleState = "running" | "paused" | "stopped" | "aborted" | "failed";
|
|
25
|
+
export type QueueState = Readonly<{
|
|
26
|
+
rate: number;
|
|
27
|
+
pending: number;
|
|
28
|
+
active: number;
|
|
29
|
+
state: QueueLifecycleState;
|
|
30
|
+
started: number;
|
|
31
|
+
succeeded: number;
|
|
32
|
+
failed: number;
|
|
33
|
+
retried: number;
|
|
34
|
+
/** Submitted items whose result was rejected by `cancel()`. */
|
|
35
|
+
canceled: number;
|
|
36
|
+
rateIncreases: number;
|
|
37
|
+
rateDecreases: number;
|
|
38
|
+
}>;
|
|
39
|
+
export type ThrottleHandle = ThrottleFn & {
|
|
40
|
+
submit: <T>(callback: TaskCallback<T>) => TaskHandle<T>;
|
|
41
|
+
pause: () => void;
|
|
42
|
+
resume: () => void;
|
|
43
|
+
stop: () => void;
|
|
44
|
+
abort: () => void;
|
|
45
|
+
waitForIdle: () => Promise<void>;
|
|
46
|
+
getState: () => QueueState;
|
|
47
|
+
readonly pending: number;
|
|
48
|
+
};
|
|
49
|
+
export type SchedulerOptions = PendingWorkOptions & {
|
|
50
|
+
/** Most callbacks awaiting asynchronous settlement at once. */
|
|
51
|
+
concurrency: number;
|
|
52
|
+
};
|
|
53
|
+
export declare function createScheduler(options: SchedulerOptions, adaptiveRateOptions: AdaptiveRateOptions, retryPolicy: RetryPolicy): ThrottleHandle;
|
package/dist/scheduler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAdaptiveRate } from "./adaptive-rate.js";
|
|
2
2
|
import { createPendingWork } from "./pending-work.js";
|
|
3
|
+
const returnedFalse = Object.freeze({ kind: "returned-false" });
|
|
3
4
|
/** The state each event leads to. An event missing from the current state's row is a no-op. */
|
|
4
5
|
const lifecycleTransitions = {
|
|
5
6
|
running: { pause: "paused", stop: "stopped", abort: "aborted", fail: "failed" },
|
|
@@ -9,26 +10,28 @@ const lifecycleTransitions = {
|
|
|
9
10
|
failed: {},
|
|
10
11
|
};
|
|
11
12
|
export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
12
|
-
const { concurrency,
|
|
13
|
+
const { concurrency, ...pendingWorkOptions } = options;
|
|
13
14
|
let lifecycle = { state: "running" };
|
|
14
15
|
let cnt_started = 0;
|
|
15
16
|
let cnt_succeeded = 0;
|
|
16
17
|
let cnt_failed = 0;
|
|
17
18
|
let cnt_retried = 0;
|
|
19
|
+
let cnt_canceled = 0;
|
|
18
20
|
const abortController = new AbortController();
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
+
/** Submitted items whose result has not settled. */
|
|
22
|
+
const tasks = new Set();
|
|
23
|
+
const work = createPendingWork(pendingWorkOptions, { retryQueued: start });
|
|
21
24
|
const adaptiveRate = createAdaptiveRate(adaptiveRateOptions, {
|
|
22
25
|
hasPendingWork: () => work.queued > 0,
|
|
23
26
|
startsDue(limit) {
|
|
24
27
|
const batch = Math.min(limit, work.queued);
|
|
25
|
-
for (let started = 0; started < batch && work.active <
|
|
28
|
+
for (let started = 0; started < batch && work.active < concurrency; started++) {
|
|
26
29
|
const item = work.take();
|
|
27
30
|
if (item === undefined)
|
|
28
31
|
break;
|
|
29
32
|
execute(item);
|
|
30
33
|
}
|
|
31
|
-
return work.active <
|
|
34
|
+
return work.active < concurrency;
|
|
32
35
|
},
|
|
33
36
|
failed(error) {
|
|
34
37
|
transition("fail", error);
|
|
@@ -57,69 +60,135 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
57
60
|
adaptiveRate.stop();
|
|
58
61
|
work.discard();
|
|
59
62
|
abortController.abort();
|
|
63
|
+
rejectTasks(abortController.signal.reason);
|
|
60
64
|
return;
|
|
61
65
|
case "failed":
|
|
62
66
|
adaptiveRate.stop();
|
|
63
67
|
work.fail(error);
|
|
68
|
+
rejectTasks(error);
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
/** Rejects every unsettled submitted item with `reason` and aborts its signal. */
|
|
72
|
+
function rejectTasks(reason) {
|
|
73
|
+
// Snapshot first: abort listeners run synchronously and may re-enter the queue.
|
|
74
|
+
const affected = [...tasks];
|
|
75
|
+
tasks.clear();
|
|
76
|
+
for (const task of affected) {
|
|
77
|
+
task.controller.abort(reason);
|
|
78
|
+
task.reject(reason);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function nextAttempt(item, outcome, value) {
|
|
82
|
+
const { task } = item;
|
|
83
|
+
if (outcome) {
|
|
84
|
+
const decision = retryPolicy.decide(outcome, item.attempt);
|
|
85
|
+
if (decision && (task === undefined || tasks.has(task))) {
|
|
86
|
+
cnt_retried++;
|
|
87
|
+
item.attempt++;
|
|
88
|
+
if (task)
|
|
89
|
+
task.active = false;
|
|
90
|
+
return { item, ...decision };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (task && tasks.delete(task)) {
|
|
94
|
+
// Submitted items never return-false, so a failure always carries its error.
|
|
95
|
+
if (outcome)
|
|
96
|
+
task.reject("error" in outcome ? outcome.error : undefined);
|
|
97
|
+
else
|
|
98
|
+
task.resolve(value);
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
74
101
|
}
|
|
75
|
-
function handleSettlement(item, outcome, reportSettlement, resume = false) {
|
|
102
|
+
function handleSettlement(item, outcome, value, reportSettlement, resume = false) {
|
|
76
103
|
if (lifecycle.state === "aborted" || lifecycle.state === "failed") {
|
|
77
104
|
work.settle();
|
|
78
105
|
return;
|
|
79
106
|
}
|
|
80
|
-
|
|
81
|
-
cnt_failed++;
|
|
82
|
-
else
|
|
83
|
-
cnt_succeeded++;
|
|
84
|
-
work.settle(() => nextAttempt(item, outcome), () => {
|
|
107
|
+
const observe = () => {
|
|
85
108
|
reportSettlement(outcome);
|
|
86
109
|
// An async settlement frees a slot; a start may be due if concurrency held one back.
|
|
87
110
|
if (resume)
|
|
88
111
|
start();
|
|
89
|
-
}
|
|
112
|
+
};
|
|
113
|
+
if (item.task && !tasks.has(item.task)) {
|
|
114
|
+
// Canceled: the attempt's outcome neither counts nor retries, but the observation window still closes.
|
|
115
|
+
outcome = undefined;
|
|
116
|
+
work.settle(undefined, observe);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (outcome)
|
|
120
|
+
cnt_failed++;
|
|
121
|
+
else
|
|
122
|
+
cnt_succeeded++;
|
|
123
|
+
work.settle(() => nextAttempt(item, outcome, value), observe);
|
|
124
|
+
}
|
|
125
|
+
/** Only fire-and-forget callbacks report failure by returning `false`. */
|
|
126
|
+
function returnedOutcome(item, value) {
|
|
127
|
+
return value === false && item.task === undefined ? returnedFalse : undefined;
|
|
90
128
|
}
|
|
91
129
|
function execute(item) {
|
|
92
130
|
const reportSettlement = adaptiveRate.started();
|
|
93
131
|
cnt_started++;
|
|
132
|
+
const { task } = item;
|
|
133
|
+
if (task)
|
|
134
|
+
task.active = true;
|
|
94
135
|
let result;
|
|
95
136
|
try {
|
|
96
|
-
result = item.fn({ signal: abortController.signal });
|
|
137
|
+
result = item.fn({ signal: task?.controller.signal ?? abortController.signal, attempt: item.attempt });
|
|
97
138
|
}
|
|
98
139
|
catch (error) {
|
|
99
|
-
handleSettlement(item, { kind: "thrown", error }, reportSettlement);
|
|
140
|
+
handleSettlement(item, { kind: "thrown", error }, undefined, reportSettlement);
|
|
100
141
|
return;
|
|
101
142
|
}
|
|
102
143
|
if (result instanceof Promise) {
|
|
103
|
-
void result.then(value => handleSettlement(item, value
|
|
144
|
+
void result.then((value) => handleSettlement(item, returnedOutcome(item, value), value, reportSettlement, true), (error) => handleSettlement(item, { kind: "rejected", error }, undefined, reportSettlement, true));
|
|
104
145
|
return;
|
|
105
146
|
}
|
|
106
|
-
handleSettlement(item, result
|
|
147
|
+
handleSettlement(item, returnedOutcome(item, result), result, reportSettlement);
|
|
107
148
|
}
|
|
108
149
|
function start() {
|
|
109
150
|
if (lifecycle.state === "running")
|
|
110
151
|
adaptiveRate.start();
|
|
111
152
|
}
|
|
112
|
-
|
|
153
|
+
/** Admits `item` or throws: the queue is terminal or every capacity reservation is taken. */
|
|
154
|
+
function accept(item) {
|
|
113
155
|
if (lifecycle.state === "aborted")
|
|
114
156
|
throw new Error("Cannot enqueue work after the queue has been aborted");
|
|
115
157
|
if (lifecycle.state === "failed")
|
|
116
158
|
throw lifecycle.error;
|
|
117
|
-
work.accept(
|
|
159
|
+
work.accept(item);
|
|
160
|
+
}
|
|
161
|
+
/** Schedules newly accepted work. */
|
|
162
|
+
function schedule() {
|
|
118
163
|
if (lifecycle.state === "stopped")
|
|
119
164
|
transition("restart");
|
|
120
165
|
else
|
|
121
166
|
start();
|
|
122
167
|
}
|
|
168
|
+
function enqueue(callback) {
|
|
169
|
+
accept({ fn: callback, attempt: 1 });
|
|
170
|
+
schedule();
|
|
171
|
+
}
|
|
172
|
+
enqueue.submit = (callback) => {
|
|
173
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
174
|
+
const task = { controller: new AbortController(), resolve: resolve, reject, active: false };
|
|
175
|
+
const item = { fn: callback, attempt: 1, task };
|
|
176
|
+
accept(item);
|
|
177
|
+
tasks.add(task);
|
|
178
|
+
schedule();
|
|
179
|
+
return {
|
|
180
|
+
result: promise,
|
|
181
|
+
cancel(reason) {
|
|
182
|
+
if (!tasks.delete(task))
|
|
183
|
+
return;
|
|
184
|
+
cnt_canceled++;
|
|
185
|
+
if (!task.active)
|
|
186
|
+
work.remove(item);
|
|
187
|
+
task.controller.abort(reason);
|
|
188
|
+
task.reject(task.controller.signal.reason);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
};
|
|
123
192
|
enqueue.pause = () => transition("pause");
|
|
124
193
|
enqueue.resume = () => transition("resume");
|
|
125
194
|
enqueue.stop = () => transition("stop");
|
|
@@ -138,6 +207,7 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
138
207
|
succeeded: cnt_succeeded,
|
|
139
208
|
failed: cnt_failed,
|
|
140
209
|
retried: cnt_retried,
|
|
210
|
+
canceled: cnt_canceled,
|
|
141
211
|
rateIncreases: adaptiveRate.rateIncreases,
|
|
142
212
|
rateDecreases: adaptiveRate.rateDecreases,
|
|
143
213
|
});
|
package/dist/scheduler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,2BAA2B;
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dynamic-throttled-queue",
|
|
3
|
-
"version": "2.3.0-rc.
|
|
3
|
+
"version": "2.3.0-rc.63bc2b5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dynamically throttles arbitrary code to execute between a minimum and maximum number of times per interval. Best for making throttled API requests.",
|
|
6
6
|
"files": [
|