dynamic-throttled-queue 2.3.0-rc.e78fc2f → 3.0.0-rc.1d87d99
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 +298 -156
- package/dist/adaptive-rate.d.ts +56 -21
- package/dist/adaptive-rate.js +123 -22
- package/dist/adaptive-rate.js.map +1 -1
- package/dist/dynamic-throttled-queue.d.ts +9 -67
- package/dist/dynamic-throttled-queue.js +23 -50
- 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 +25 -0
- package/dist/retry-policy.js +73 -0
- package/dist/retry-policy.js.map +1 -0
- package/dist/scheduler.d.ts +61 -2
- package/dist/scheduler.js +164 -76
- package/dist/scheduler.js.map +1 -1
- package/package.json +7 -7
- package/dist/retry-backoff.d.ts +0 -2
- package/dist/retry-backoff.js +0 -31
- package/dist/retry-backoff.js.map +0 -1
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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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FailureOutcome } from "./adaptive-rate.js";
|
|
2
|
+
export type RetryBackoff = {
|
|
3
|
+
strategy: "fixed" | "linear" | "exponential";
|
|
4
|
+
baseDelay: number;
|
|
5
|
+
maxDelay?: number;
|
|
6
|
+
jitter?: number;
|
|
7
|
+
random?: () => number;
|
|
8
|
+
};
|
|
9
|
+
export type RetryClassifier = (outcome: FailureOutcome, attempt: number) => boolean;
|
|
10
|
+
export type RetryPolicyOptions = {
|
|
11
|
+
retry?: number;
|
|
12
|
+
retryBackoff?: RetryBackoff;
|
|
13
|
+
retryClassifier?: RetryClassifier;
|
|
14
|
+
};
|
|
15
|
+
/** How a failed attempt continues: `{}` retries now, `{ delay }` retries after `delay` ms. */
|
|
16
|
+
type RetryDecision = Readonly<{
|
|
17
|
+
delay?: number;
|
|
18
|
+
}>;
|
|
19
|
+
export type RetryPolicy = {
|
|
20
|
+
/** Decides whether failed attempt `attempt` (one-based) gets another attempt. `undefined` drops it. */
|
|
21
|
+
decide: (outcome: FailureOutcome, attempt: number) => RetryDecision | undefined;
|
|
22
|
+
};
|
|
23
|
+
/** Validates the retry options, then decides each failed attempt: drop, retry now, or retry after a delay. */
|
|
24
|
+
export declare function createRetryPolicy({ retry, retryBackoff, retryClassifier }: RetryPolicyOptions): RetryPolicy;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const strategies = { fixed: true, linear: true, exponential: true };
|
|
2
|
+
const retryNow = Object.freeze({});
|
|
3
|
+
function validate(retry, retryBackoff) {
|
|
4
|
+
if (!Number.isInteger(retry) || retry < 0) {
|
|
5
|
+
throw new Error("retry must be a non-negative integer");
|
|
6
|
+
}
|
|
7
|
+
if (retryBackoff === undefined)
|
|
8
|
+
return;
|
|
9
|
+
if (!Object.hasOwn(strategies, retryBackoff.strategy)) {
|
|
10
|
+
throw new Error("retryBackoff.strategy must be fixed, linear, or exponential");
|
|
11
|
+
}
|
|
12
|
+
if (!Number.isFinite(retryBackoff.baseDelay) || retryBackoff.baseDelay < 0) {
|
|
13
|
+
throw new Error("retryBackoff.baseDelay must be a finite non-negative number");
|
|
14
|
+
}
|
|
15
|
+
if (retryBackoff.maxDelay !== undefined && (!Number.isFinite(retryBackoff.maxDelay) || retryBackoff.maxDelay < 0)) {
|
|
16
|
+
throw new Error("retryBackoff.maxDelay must be a finite non-negative number");
|
|
17
|
+
}
|
|
18
|
+
if (retryBackoff.jitter !== undefined && (!Number.isFinite(retryBackoff.jitter) || retryBackoff.jitter < 0 || retryBackoff.jitter > 1)) {
|
|
19
|
+
throw new Error("retryBackoff.jitter must be a finite number from 0 through 1");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function readRandom(random) {
|
|
23
|
+
try {
|
|
24
|
+
// eslint-disable-next-line sonarjs/pseudo-random -- Default jitter requires a random source.
|
|
25
|
+
const value = random?.() ?? Math.random();
|
|
26
|
+
return Number.isFinite(value) && value >= 0 && value <= 1 ? value : undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function calculateRetryDelay(policy, retryIndex) {
|
|
33
|
+
let delay;
|
|
34
|
+
switch (policy.strategy) {
|
|
35
|
+
case "linear":
|
|
36
|
+
delay = policy.baseDelay * retryIndex;
|
|
37
|
+
break;
|
|
38
|
+
case "exponential":
|
|
39
|
+
delay = policy.baseDelay * 2 ** (retryIndex - 1);
|
|
40
|
+
break;
|
|
41
|
+
default: delay = policy.baseDelay;
|
|
42
|
+
}
|
|
43
|
+
const cappedDelay = policy.maxDelay === undefined ? delay : Math.min(delay, policy.maxDelay);
|
|
44
|
+
if (policy.jitter === undefined)
|
|
45
|
+
return cappedDelay;
|
|
46
|
+
const random = readRandom(policy.random);
|
|
47
|
+
if (random === undefined)
|
|
48
|
+
return cappedDelay;
|
|
49
|
+
const jitteredDelay = cappedDelay * (1 + (random * 2 - 1) * policy.jitter);
|
|
50
|
+
return policy.maxDelay === undefined ? jitteredDelay : Math.min(jitteredDelay, policy.maxDelay);
|
|
51
|
+
}
|
|
52
|
+
/** Validates the retry options, then decides each failed attempt: drop, retry now, or retry after a delay. */
|
|
53
|
+
export function createRetryPolicy({ retry = 0, retryBackoff, retryClassifier }) {
|
|
54
|
+
validate(retry, retryBackoff);
|
|
55
|
+
function isRetryable(outcome, attempt) {
|
|
56
|
+
if (!retryClassifier)
|
|
57
|
+
return true;
|
|
58
|
+
try {
|
|
59
|
+
return retryClassifier(outcome, attempt) === true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
decide(outcome, attempt) {
|
|
67
|
+
if (attempt > retry || !isRetryable(outcome, attempt))
|
|
68
|
+
return undefined;
|
|
69
|
+
return retryBackoff === undefined ? retryNow : { delay: calculateRetryDelay(retryBackoff, attempt) };
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=retry-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-policy.js","sourceRoot":"","sources":["../src/retry-policy.ts"],"names":[],"mappings":"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"}
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
1
|
import type { AdaptiveRateOptions } from "./adaptive-rate.js";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { PendingWorkOptions } from "./pending-work.js";
|
|
3
|
+
import type { RetryPolicy } from "./retry-policy.js";
|
|
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 (counts as an error, retries if configured). Any other value is success. */
|
|
10
|
+
export type ThrottleCallback = (context: ExecutionContext) => unknown;
|
|
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
|
+
/** Milliseconds, rounded up, until a server-directed cooldown ends; `0` when none is active. */
|
|
39
|
+
cooldownRemaining: number;
|
|
40
|
+
/** `cooldownFor()` calls that moved the cooldown deadline later. */
|
|
41
|
+
cooldowns: number;
|
|
42
|
+
/** Total milliseconds those calls added to the cooldown deadline. */
|
|
43
|
+
cooldownTotal: number;
|
|
44
|
+
}>;
|
|
45
|
+
export type ThrottleHandle = ThrottleFn & {
|
|
46
|
+
submit: <T>(callback: TaskCallback<T>) => TaskHandle<T>;
|
|
47
|
+
pause: () => void;
|
|
48
|
+
resume: () => void;
|
|
49
|
+
stop: () => void;
|
|
50
|
+
abort: () => void;
|
|
51
|
+
/** Starts nothing new for `delay` ms (capped at `maxCooldown`); never shortens an active cooldown. */
|
|
52
|
+
cooldownFor: (delay: number) => void;
|
|
53
|
+
waitForIdle: () => Promise<void>;
|
|
54
|
+
getState: () => QueueState;
|
|
55
|
+
};
|
|
56
|
+
export type SchedulerOptions = PendingWorkOptions & {
|
|
57
|
+
/** Most callbacks awaiting asynchronous settlement at once. */
|
|
58
|
+
concurrency: number;
|
|
59
|
+
/** Longest cooldown, in ms, that `cooldownFor()` applies; longer requests are clamped to it. */
|
|
60
|
+
maxCooldown: number;
|
|
61
|
+
};
|
|
62
|
+
export declare function createScheduler(options: SchedulerOptions, adaptiveRateOptions: AdaptiveRateOptions, retryPolicy: RetryPolicy): ThrottleHandle;
|
package/dist/scheduler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAdaptiveRate } from "./adaptive-rate.js";
|
|
2
2
|
import { createPendingWork } from "./pending-work.js";
|
|
3
|
-
|
|
3
|
+
const returnedFalse = Object.freeze({ kind: "returned-false" });
|
|
4
4
|
/** The state each event leads to. An event missing from the current state's row is a no-op. */
|
|
5
5
|
const lifecycleTransitions = {
|
|
6
6
|
running: { pause: "paused", stop: "stopped", abort: "aborted", fail: "failed" },
|
|
@@ -9,40 +9,40 @@ const lifecycleTransitions = {
|
|
|
9
9
|
aborted: {},
|
|
10
10
|
failed: {},
|
|
11
11
|
};
|
|
12
|
-
export function createScheduler(options, adaptiveRateOptions) {
|
|
13
|
-
const {
|
|
14
|
-
let last_called = 0;
|
|
15
|
-
let timeout;
|
|
12
|
+
export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
13
|
+
const { concurrency, maxCooldown, ...pendingWorkOptions } = options;
|
|
16
14
|
let lifecycle = { state: "running" };
|
|
17
15
|
let cnt_started = 0;
|
|
18
16
|
let cnt_succeeded = 0;
|
|
19
17
|
let cnt_failed = 0;
|
|
20
18
|
let cnt_retried = 0;
|
|
19
|
+
let cnt_canceled = 0;
|
|
20
|
+
let cnt_cooldowns = 0;
|
|
21
|
+
let cooldownTotal = 0;
|
|
22
|
+
/** Monotonic (`performance.now()`) time before which nothing starts; `undefined` when no cooldown is active. */
|
|
23
|
+
let cooldownDeadline;
|
|
24
|
+
/** Fires at the deadline. Cleared while stopped, which keeps the deadline for the restart. */
|
|
25
|
+
let cooldownTimer;
|
|
21
26
|
const abortController = new AbortController();
|
|
22
|
-
|
|
23
|
-
const
|
|
27
|
+
/** Submitted items whose result has not settled. */
|
|
28
|
+
const tasks = new Set();
|
|
29
|
+
const work = createPendingWork(pendingWorkOptions, { retryQueued: start });
|
|
24
30
|
const adaptiveRate = createAdaptiveRate(adaptiveRateOptions, {
|
|
25
31
|
hasPendingWork: () => work.queued > 0,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
idle() {
|
|
36
|
-
clearTimeout(timeout);
|
|
37
|
-
timeout = undefined;
|
|
32
|
+
startsDue(limit) {
|
|
33
|
+
const batch = Math.min(limit, work.queued);
|
|
34
|
+
for (let started = 0; started < batch && work.active < concurrency && cooldownDeadline === undefined; started++) {
|
|
35
|
+
const item = work.take();
|
|
36
|
+
if (item === undefined)
|
|
37
|
+
break;
|
|
38
|
+
execute(item);
|
|
39
|
+
}
|
|
40
|
+
return work.active < concurrency;
|
|
38
41
|
},
|
|
39
42
|
failed(error) {
|
|
40
43
|
transition("fail", error);
|
|
41
44
|
},
|
|
42
45
|
});
|
|
43
|
-
function spacing() {
|
|
44
|
-
return evenly_spaced ? interval / adaptiveRate.rate : interval;
|
|
45
|
-
}
|
|
46
46
|
/** Moves the lifecycle for `event` and runs the cleanup that entering the new state requires. */
|
|
47
47
|
function transition(event, error) {
|
|
48
48
|
const next = lifecycleTransitions[lifecycle.state][event];
|
|
@@ -52,120 +52,205 @@ export function createScheduler(options, adaptiveRateOptions) {
|
|
|
52
52
|
switch (next) {
|
|
53
53
|
case "running":
|
|
54
54
|
work.thaw();
|
|
55
|
-
|
|
55
|
+
if (cooldownDeadline === undefined) {
|
|
56
|
+
adaptiveRate.start();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
armCooldown(cooldownDeadline);
|
|
60
|
+
adaptiveRate.suspend();
|
|
56
61
|
return;
|
|
57
62
|
case "paused":
|
|
63
|
+
// A cooldown keeps elapsing while paused: its deadline is the server's, not the queue's.
|
|
58
64
|
work.freeze();
|
|
59
65
|
adaptiveRate.pause();
|
|
60
66
|
return;
|
|
61
67
|
case "stopped":
|
|
68
|
+
clearTimeout(cooldownTimer);
|
|
69
|
+
cooldownTimer = undefined;
|
|
62
70
|
work.freeze();
|
|
63
71
|
adaptiveRate.stop();
|
|
64
72
|
return;
|
|
65
73
|
case "aborted":
|
|
74
|
+
clearCooldown();
|
|
66
75
|
adaptiveRate.stop();
|
|
67
76
|
work.discard();
|
|
68
77
|
abortController.abort();
|
|
78
|
+
rejectTasks(abortController.signal.reason);
|
|
69
79
|
return;
|
|
70
80
|
case "failed":
|
|
81
|
+
clearCooldown();
|
|
71
82
|
adaptiveRate.stop();
|
|
72
83
|
work.fail(error);
|
|
84
|
+
rejectTasks(error);
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
function armCooldown(deadline) {
|
|
88
|
+
clearTimeout(cooldownTimer);
|
|
89
|
+
cooldownTimer = setTimeout(expireCooldown, Math.max(0, deadline - performance.now()));
|
|
90
|
+
}
|
|
91
|
+
function expireCooldown() {
|
|
92
|
+
// A timer can fire slightly before the monotonic deadline; wait out the remainder.
|
|
93
|
+
if (cooldownDeadline !== undefined && performance.now() < cooldownDeadline) {
|
|
94
|
+
armCooldown(cooldownDeadline);
|
|
95
|
+
return;
|
|
80
96
|
}
|
|
81
|
-
|
|
82
|
-
|
|
97
|
+
cooldownTimer = undefined;
|
|
98
|
+
cooldownDeadline = undefined;
|
|
99
|
+
start();
|
|
100
|
+
}
|
|
101
|
+
function clearCooldown() {
|
|
102
|
+
clearTimeout(cooldownTimer);
|
|
103
|
+
cooldownTimer = undefined;
|
|
104
|
+
cooldownDeadline = undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Rejects every unsettled submitted item with `reason` and aborts its signal. */
|
|
107
|
+
function rejectTasks(reason) {
|
|
108
|
+
// Snapshot first: abort listeners run synchronously and may re-enter the queue.
|
|
109
|
+
const affected = [...tasks];
|
|
110
|
+
tasks.clear();
|
|
111
|
+
for (const task of affected) {
|
|
112
|
+
task.controller.abort(reason);
|
|
113
|
+
task.reject(reason);
|
|
83
114
|
}
|
|
84
115
|
}
|
|
85
|
-
function nextAttempt(item, outcome) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
function nextAttempt(item, outcome, value) {
|
|
117
|
+
const { task } = item;
|
|
118
|
+
if (outcome) {
|
|
119
|
+
const decision = retryPolicy.decide(outcome, item.attempt);
|
|
120
|
+
if (decision && (task === undefined || tasks.has(task))) {
|
|
121
|
+
cnt_retried++;
|
|
122
|
+
item.attempt++;
|
|
123
|
+
if (task)
|
|
124
|
+
task.active = false;
|
|
125
|
+
return { item, ...decision };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (task && tasks.delete(task)) {
|
|
129
|
+
// Submitted items never return-false, so a failure always carries its error.
|
|
130
|
+
if (outcome)
|
|
131
|
+
task.reject("error" in outcome ? outcome.error : undefined);
|
|
132
|
+
else
|
|
133
|
+
task.resolve(value);
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
93
136
|
}
|
|
94
|
-
function handleSettlement(item, outcome, reportSettlement, resume = false) {
|
|
137
|
+
function handleSettlement(item, outcome, value, reportSettlement, resume = false) {
|
|
95
138
|
if (lifecycle.state === "aborted" || lifecycle.state === "failed") {
|
|
96
139
|
work.settle();
|
|
97
140
|
return;
|
|
98
141
|
}
|
|
142
|
+
const observe = () => {
|
|
143
|
+
reportSettlement(outcome);
|
|
144
|
+
// An async settlement frees a slot; a start may be due if concurrency held one back.
|
|
145
|
+
if (resume)
|
|
146
|
+
start();
|
|
147
|
+
};
|
|
148
|
+
if (item.task && !tasks.has(item.task)) {
|
|
149
|
+
// Canceled: the attempt's outcome neither counts nor retries, but the observation window still closes.
|
|
150
|
+
outcome = undefined;
|
|
151
|
+
work.settle(undefined, observe);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
99
154
|
if (outcome)
|
|
100
155
|
cnt_failed++;
|
|
101
156
|
else
|
|
102
157
|
cnt_succeeded++;
|
|
103
|
-
work.settle(() => nextAttempt(item, outcome),
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
158
|
+
work.settle(() => nextAttempt(item, outcome, value), observe);
|
|
159
|
+
}
|
|
160
|
+
/** Only fire-and-forget callbacks report failure by returning `false`. */
|
|
161
|
+
function returnedOutcome(item, value) {
|
|
162
|
+
return value === false && item.task === undefined ? returnedFalse : undefined;
|
|
108
163
|
}
|
|
109
164
|
function execute(item) {
|
|
110
165
|
const reportSettlement = adaptiveRate.started();
|
|
111
166
|
cnt_started++;
|
|
167
|
+
const { task } = item;
|
|
168
|
+
if (task)
|
|
169
|
+
task.active = true;
|
|
112
170
|
let result;
|
|
113
171
|
try {
|
|
114
|
-
result = item.fn({ signal: abortController.signal });
|
|
172
|
+
result = item.fn({ signal: task?.controller.signal ?? abortController.signal, attempt: item.attempt });
|
|
115
173
|
}
|
|
116
174
|
catch (error) {
|
|
117
|
-
handleSettlement(item, { kind: "thrown", error }, reportSettlement);
|
|
175
|
+
handleSettlement(item, { kind: "thrown", error }, undefined, reportSettlement);
|
|
118
176
|
return;
|
|
119
177
|
}
|
|
120
178
|
if (result instanceof Promise) {
|
|
121
|
-
void result.then(value => handleSettlement(item, value
|
|
179
|
+
void result.then((value) => handleSettlement(item, returnedOutcome(item, value), value, reportSettlement, true), (error) => handleSettlement(item, { kind: "rejected", error }, undefined, reportSettlement, true));
|
|
122
180
|
return;
|
|
123
181
|
}
|
|
124
|
-
handleSettlement(item, result
|
|
125
|
-
}
|
|
126
|
-
function dequeue() {
|
|
127
|
-
const threshold = last_called + spacing();
|
|
128
|
-
const now = Date.now();
|
|
129
|
-
if (now < threshold) {
|
|
130
|
-
clearTimeout(timeout);
|
|
131
|
-
timeout = setTimeout(dequeue, threshold - now);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const batch = Math.min(evenly_spaced ? 1 : adaptiveRate.rate, work.queued);
|
|
135
|
-
let started = 0;
|
|
136
|
-
while (started < batch && work.active < max_concurrency) {
|
|
137
|
-
const item = work.take();
|
|
138
|
-
if (item === undefined)
|
|
139
|
-
break;
|
|
140
|
-
if (started++ === 0)
|
|
141
|
-
last_called = Date.now();
|
|
142
|
-
execute(item);
|
|
143
|
-
}
|
|
144
|
-
if (work.queued === 0)
|
|
145
|
-
adaptiveRate.drained();
|
|
146
|
-
if (adaptiveRate.pacing !== "open" || work.active >= max_concurrency)
|
|
147
|
-
return;
|
|
148
|
-
timeout = setTimeout(dequeue, spacing());
|
|
182
|
+
handleSettlement(item, returnedOutcome(item, result), result, reportSettlement);
|
|
149
183
|
}
|
|
150
184
|
function start() {
|
|
151
|
-
if (lifecycle.state === "running")
|
|
185
|
+
if (lifecycle.state === "running" && cooldownDeadline === undefined)
|
|
152
186
|
adaptiveRate.start();
|
|
153
187
|
}
|
|
154
|
-
|
|
188
|
+
/** Admits `item` or throws: the queue is terminal or every capacity reservation is taken. */
|
|
189
|
+
function accept(item) {
|
|
155
190
|
if (lifecycle.state === "aborted")
|
|
156
191
|
throw new Error("Cannot enqueue work after the queue has been aborted");
|
|
157
192
|
if (lifecycle.state === "failed")
|
|
158
193
|
throw lifecycle.error;
|
|
159
|
-
work.accept(
|
|
194
|
+
work.accept(item);
|
|
195
|
+
}
|
|
196
|
+
/** Schedules newly accepted work. */
|
|
197
|
+
function schedule() {
|
|
160
198
|
if (lifecycle.state === "stopped")
|
|
161
199
|
transition("restart");
|
|
162
200
|
else
|
|
163
201
|
start();
|
|
164
202
|
}
|
|
203
|
+
function enqueue(callback) {
|
|
204
|
+
accept({ fn: callback, attempt: 1 });
|
|
205
|
+
schedule();
|
|
206
|
+
}
|
|
207
|
+
enqueue.submit = (callback) => {
|
|
208
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
209
|
+
const task = { controller: new AbortController(), resolve: resolve, reject, active: false };
|
|
210
|
+
const item = { fn: callback, attempt: 1, task };
|
|
211
|
+
accept(item);
|
|
212
|
+
tasks.add(task);
|
|
213
|
+
schedule();
|
|
214
|
+
return {
|
|
215
|
+
result: promise,
|
|
216
|
+
cancel(reason) {
|
|
217
|
+
if (!tasks.delete(task))
|
|
218
|
+
return;
|
|
219
|
+
cnt_canceled++;
|
|
220
|
+
if (!task.active)
|
|
221
|
+
work.remove(item);
|
|
222
|
+
task.controller.abort(reason);
|
|
223
|
+
task.reject(task.controller.signal.reason);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
};
|
|
165
227
|
enqueue.pause = () => transition("pause");
|
|
166
228
|
enqueue.resume = () => transition("resume");
|
|
167
229
|
enqueue.stop = () => transition("stop");
|
|
168
230
|
enqueue.abort = () => transition("abort");
|
|
231
|
+
enqueue.cooldownFor = (delay) => {
|
|
232
|
+
if (!Number.isFinite(delay) || delay < 0)
|
|
233
|
+
throw new RangeError("cooldownFor delay must be a finite non-negative number");
|
|
234
|
+
if (lifecycle.state === "aborted" || lifecycle.state === "failed")
|
|
235
|
+
return;
|
|
236
|
+
const now = performance.now();
|
|
237
|
+
const latest = Math.max(now, cooldownDeadline ?? now);
|
|
238
|
+
const deadline = now + Math.min(delay, maxCooldown);
|
|
239
|
+
// Hints are absolute ("not before now + delay"), so overlapping ones keep the later deadline, never add up.
|
|
240
|
+
if (deadline <= latest)
|
|
241
|
+
return;
|
|
242
|
+
const beginning = cooldownDeadline === undefined;
|
|
243
|
+
cnt_cooldowns++;
|
|
244
|
+
cooldownTotal += deadline - latest;
|
|
245
|
+
cooldownDeadline = deadline;
|
|
246
|
+
// Stopped: the restart arms the timer for whatever remains.
|
|
247
|
+
if (lifecycle.state === "stopped")
|
|
248
|
+
return;
|
|
249
|
+
armCooldown(deadline);
|
|
250
|
+
// Paused: adaptive rate is already stopped; `resume()` suspends it instead of starting it.
|
|
251
|
+
if (beginning && lifecycle.state === "running")
|
|
252
|
+
adaptiveRate.suspend();
|
|
253
|
+
};
|
|
169
254
|
enqueue.waitForIdle = async () => {
|
|
170
255
|
if (lifecycle.state === "failed")
|
|
171
256
|
throw lifecycle.error;
|
|
@@ -180,10 +265,13 @@ export function createScheduler(options, adaptiveRateOptions) {
|
|
|
180
265
|
succeeded: cnt_succeeded,
|
|
181
266
|
failed: cnt_failed,
|
|
182
267
|
retried: cnt_retried,
|
|
268
|
+
canceled: cnt_canceled,
|
|
183
269
|
rateIncreases: adaptiveRate.rateIncreases,
|
|
184
270
|
rateDecreases: adaptiveRate.rateDecreases,
|
|
271
|
+
cooldownRemaining: cooldownDeadline === undefined ? 0 : Math.max(0, Math.ceil(cooldownDeadline - performance.now())),
|
|
272
|
+
cooldowns: cnt_cooldowns,
|
|
273
|
+
cooldownTotal,
|
|
185
274
|
});
|
|
186
|
-
Object.defineProperty(enqueue, "pending", { get: () => work.pending });
|
|
187
275
|
return enqueue;
|
|
188
276
|
}
|
|
189
277
|
//# sourceMappingURL=scheduler.js.map
|