dynamic-throttled-queue 2.1.0 → 2.1.1-rc.6f2d27a
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 +7 -3
- package/dist/dynamic-throttled-queue.d.ts +3 -2
- package/dist/dynamic-throttled-queue.js +15 -143
- package/dist/dynamic-throttled-queue.js.map +1 -1
- package/dist/rate-controller.d.ts +25 -0
- package/dist/rate-controller.js +32 -0
- package/dist/rate-controller.js.map +1 -0
- package/dist/scheduler.d.ts +3 -0
- package/dist/scheduler.js +132 -0
- package/dist/scheduler.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,13 +38,15 @@ Set `concurrency` to bound callbacks that are still awaiting asynchronous comple
|
|
|
38
38
|
| `max_rpi` | `number` | `min_rpi` | Maximum requests per interval |
|
|
39
39
|
| `interval` | `number` | *required* | Milliseconds between each batch of requests |
|
|
40
40
|
| `evenly_spaced` | `boolean` | `true` | Distribute requests evenly throughout the interval |
|
|
41
|
-
| `errors_per_interval` | `number` | `5` |
|
|
41
|
+
| `errors_per_interval` | `number` | `5` | Positive integer error threshold per interval before adjusting rate |
|
|
42
42
|
| `back_off` | `boolean` | `false` | Back off for 1 full interval when error threshold is hit |
|
|
43
|
-
| `retry` | `number` | `0` |
|
|
43
|
+
| `retry` | `number` | `0` | Non-negative integer number of times to retry failed callbacks |
|
|
44
44
|
| `concurrency` | `number` | — | Maximum callbacks awaiting asynchronous completion; omit for no limit |
|
|
45
|
-
| `compact_threshold` | `number` | `512` |
|
|
45
|
+
| `compact_threshold` | `number` | `512` | Non-negative integer minimum dead slots before internal queue compaction triggers; `0` compacts at the earliest eligible point |
|
|
46
46
|
| `onRateChange` | `(rate: number) => void` | — | Called when the current rate changes |
|
|
47
47
|
|
|
48
|
+
`retry`, `errors_per_interval`, and `compact_threshold` reject fractional and non-finite values. `errors_per_interval` must be at least `1`; `retry` and `compact_threshold` may be `0`.
|
|
49
|
+
|
|
48
50
|
## Handle API
|
|
49
51
|
|
|
50
52
|
`createThrottledQueue` returns a function with additional properties:
|
|
@@ -63,6 +65,8 @@ console.log(throttle.pending); // number of queued callbacks
|
|
|
63
65
|
throttle.stop(); // halt processing
|
|
64
66
|
```
|
|
65
67
|
|
|
68
|
+
`stop()` clears scheduler timers and retains callbacks that have not started. It cannot cancel an active asynchronous callback; if that callback later succeeds, returns `false`, or rejects, its settlement does not restart scheduling. A retry created by a failed active callback is retained with the pending work. Enqueueing another callback resumes the queue.
|
|
69
|
+
|
|
66
70
|
## Examples
|
|
67
71
|
|
|
68
72
|
### Basic
|
|
@@ -5,13 +5,14 @@ export type ThrottleOptions = {
|
|
|
5
5
|
interval: number;
|
|
6
6
|
max_rpi?: number;
|
|
7
7
|
evenly_spaced?: boolean;
|
|
8
|
-
/**
|
|
8
|
+
/** Positive integer error threshold per interval before rate decrease. Default 5. */
|
|
9
9
|
errors_per_interval?: number;
|
|
10
10
|
back_off?: boolean;
|
|
11
|
+
/** Non-negative integer retries for each failed callback. Default 0. */
|
|
11
12
|
retry?: number;
|
|
12
13
|
/** Maximum number of callbacks awaiting asynchronous settlement. Omit for no limit. */
|
|
13
14
|
concurrency?: number;
|
|
14
|
-
/**
|
|
15
|
+
/** Non-negative integer dead slots before queue compaction triggers. Default 512. */
|
|
15
16
|
compact_threshold?: number;
|
|
16
17
|
onRateChange?: (rate: number) => void;
|
|
17
18
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { createRateController } from "./rate-controller.js";
|
|
2
|
+
import { createScheduler } from "./scheduler.js";
|
|
1
3
|
export function createThrottledQueue(options) {
|
|
2
|
-
const { min_rpi, interval, max_rpi = min_rpi,
|
|
4
|
+
const { min_rpi, interval, max_rpi = min_rpi, concurrency, retry = 0, compact_threshold = 512 } = options;
|
|
3
5
|
const errors_per_interval = options.errors_per_interval ?? 5;
|
|
4
6
|
if (!Number.isInteger(min_rpi) || min_rpi < 1) {
|
|
5
7
|
throw new Error("min_rpi must be a positive integer");
|
|
@@ -13,150 +15,20 @@ export function createThrottledQueue(options) {
|
|
|
13
15
|
if (concurrency !== undefined && (!Number.isInteger(concurrency) || concurrency < 1)) {
|
|
14
16
|
throw new Error("concurrency must be a positive integer");
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let dyn_requests_per_interval = evenly_spaced ? 1 : current_rpi;
|
|
19
|
-
let error_count = 0;
|
|
20
|
-
let skippedLast = false;
|
|
21
|
-
let isRunning = false;
|
|
22
|
-
let last_called = 0;
|
|
23
|
-
let timeout;
|
|
24
|
-
let dynTimeout;
|
|
25
|
-
let active_count = 0;
|
|
26
|
-
const max_concurrency = concurrency ?? Infinity;
|
|
27
|
-
const queue = [];
|
|
28
|
-
let head = 0;
|
|
29
|
-
/** Halts timers. Retains unprocessed queue items; next enqueue resumes from where it left off. */
|
|
30
|
-
function stop() {
|
|
31
|
-
isRunning = false;
|
|
32
|
-
skippedLast = false;
|
|
33
|
-
clearTimeout(timeout);
|
|
34
|
-
timeout = undefined;
|
|
35
|
-
clearTimeout(dynTimeout);
|
|
36
|
-
dynTimeout = undefined;
|
|
37
|
-
if (head >= queue.length) {
|
|
38
|
-
queue.length = 0;
|
|
39
|
-
head = 0;
|
|
40
|
-
}
|
|
18
|
+
if (!Number.isInteger(errors_per_interval) || errors_per_interval < 1) {
|
|
19
|
+
throw new Error("errors_per_interval must be a positive integer");
|
|
41
20
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
error_count++;
|
|
45
|
-
if (item.retries > 0) {
|
|
46
|
-
queue.push({ fn: item.fn, retries: item.retries - 1 });
|
|
47
|
-
if (!isRunning && queue.length > head) {
|
|
48
|
-
start();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
21
|
+
if (!Number.isInteger(retry) || retry < 0) {
|
|
22
|
+
throw new Error("retry must be a non-negative integer");
|
|
52
23
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
handleResult(item, result);
|
|
56
|
-
// A released slot can unblock pending work; dequeue still observes rate pacing.
|
|
57
|
-
if (resume && isRunning && !skippedLast && queue.length > head) {
|
|
58
|
-
dequeue();
|
|
59
|
-
}
|
|
24
|
+
if (!Number.isInteger(compact_threshold) || compact_threshold < 0) {
|
|
25
|
+
throw new Error("compact_threshold must be a non-negative integer");
|
|
60
26
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
// Retries appended by settlement are outside this batch and need another start opportunity.
|
|
70
|
-
const end = Math.min(head + dyn_requests_per_interval, queue.length);
|
|
71
|
-
let started = 0;
|
|
72
|
-
while (head < end && active_count < max_concurrency) {
|
|
73
|
-
const item = queue[head++];
|
|
74
|
-
active_count++;
|
|
75
|
-
if (started++ === 0) {
|
|
76
|
-
last_called = Date.now();
|
|
77
|
-
}
|
|
78
|
-
let result;
|
|
79
|
-
try {
|
|
80
|
-
result = item.fn();
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
handleSettlement(item, false);
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (result instanceof Promise) {
|
|
87
|
-
void result.then(value => handleSettlement(item, value, true), () => handleSettlement(item, false, true));
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
handleSettlement(item, result);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// ponytail: splice is O(n), amortized by only firing when head > half array length
|
|
94
|
-
if (head > compact_threshold && head > queue.length / 2) {
|
|
95
|
-
queue.splice(0, head);
|
|
96
|
-
head = 0;
|
|
97
|
-
}
|
|
98
|
-
if (head >= queue.length) {
|
|
99
|
-
stop();
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (active_count >= max_concurrency) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
timeout = setTimeout(dequeue, dyn_interval);
|
|
106
|
-
}
|
|
107
|
-
function applyRate(newRpi) {
|
|
108
|
-
if (newRpi === current_rpi)
|
|
109
|
-
return;
|
|
110
|
-
current_rpi = newRpi;
|
|
111
|
-
onRateChange?.(current_rpi);
|
|
112
|
-
if (evenly_spaced) {
|
|
113
|
-
dyn_interval = interval / current_rpi;
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
dyn_requests_per_interval = current_rpi;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// ponytail: async callbacks resolve after adjustRate fires — error_count may lag by one interval under async load
|
|
120
|
-
function adjustRate() {
|
|
121
|
-
dynTimeout = undefined;
|
|
122
|
-
const wasSkipped = skippedLast;
|
|
123
|
-
skippedLast = false;
|
|
124
|
-
if (error_count >= errors_per_interval) {
|
|
125
|
-
applyRate(Math.max(min_rpi, current_rpi - 1));
|
|
126
|
-
if (isRunning && back_off && !wasSkipped) {
|
|
127
|
-
clearTimeout(timeout);
|
|
128
|
-
skippedLast = true;
|
|
129
|
-
timeout = setTimeout(dequeue, dyn_interval + interval);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
else if (!wasSkipped && error_count === 0 && queue.length > head) {
|
|
133
|
-
applyRate(Math.min(max_rpi, current_rpi + 1));
|
|
134
|
-
}
|
|
135
|
-
error_count = 0;
|
|
136
|
-
if (isRunning) {
|
|
137
|
-
dynTimeout = setTimeout(adjustRate, interval);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function start() {
|
|
141
|
-
if (skippedLast)
|
|
142
|
-
return;
|
|
143
|
-
isRunning = true;
|
|
144
|
-
last_called = Date.now();
|
|
145
|
-
clearTimeout(timeout);
|
|
146
|
-
timeout = setTimeout(dequeue, dyn_interval);
|
|
147
|
-
if (!dynTimeout) {
|
|
148
|
-
dynTimeout = setTimeout(adjustRate, interval);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function enqueue(callback) {
|
|
152
|
-
queue.push({ fn: callback, retries: retry });
|
|
153
|
-
if (!isRunning) {
|
|
154
|
-
start();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
enqueue.stop = stop;
|
|
158
|
-
// ponytail: defineProperty needed for getter; stop is plain assignment
|
|
159
|
-
Object.defineProperty(enqueue, "pending", { get: () => queue.length - head });
|
|
160
|
-
return enqueue;
|
|
27
|
+
return createScheduler(options, createRateController({
|
|
28
|
+
min_rpi,
|
|
29
|
+
max_rpi,
|
|
30
|
+
errors_per_interval,
|
|
31
|
+
back_off: options.back_off ?? false,
|
|
32
|
+
}));
|
|
161
33
|
}
|
|
162
34
|
//# sourceMappingURL=dynamic-throttled-queue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,6BAA6B;AAC5D,OAAO,EAAE,eAAe,EAAE,uBAAuB;AA6BjD,MAAM,UAAU,oBAAoB,CAAC,OAAwB;IAC3D,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,EAAE,WAAW,EAAE,KAAK,GAAG,CAAC,EAAE,iBAAiB,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC;IAE1G,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,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,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,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,OAAO,eAAe,CAAC,OAAO,EAAE,oBAAoB,CAAC;QACnD,OAAO;QACP,OAAO;QACP,mBAAmB;QACnB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;KACpC,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type RateControllerOptions = {
|
|
2
|
+
min_rpi: number;
|
|
3
|
+
max_rpi: number;
|
|
4
|
+
errors_per_interval: number;
|
|
5
|
+
back_off: boolean;
|
|
6
|
+
};
|
|
7
|
+
type Observation = {
|
|
8
|
+
hasPendingWork: boolean;
|
|
9
|
+
wasSkipped: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type RateDecision = {
|
|
12
|
+
rate: number;
|
|
13
|
+
shouldBackOff: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type RateStrategy = (input: RateControllerOptions & Observation & {
|
|
16
|
+
rate: number;
|
|
17
|
+
errorCount: number;
|
|
18
|
+
}) => RateDecision;
|
|
19
|
+
export type RateController = {
|
|
20
|
+
readonly rate: number;
|
|
21
|
+
recordCompletion: (result: boolean | void) => void;
|
|
22
|
+
observe: (observation: Observation) => RateDecision;
|
|
23
|
+
};
|
|
24
|
+
export declare function createRateController(options: RateControllerOptions, strategy?: RateStrategy): RateController;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const linearRateStrategy = ({ min_rpi, max_rpi, errors_per_interval, back_off, rate, errorCount, hasPendingWork, wasSkipped, }) => {
|
|
2
|
+
if (errorCount >= errors_per_interval) {
|
|
3
|
+
return {
|
|
4
|
+
rate: Math.max(min_rpi, rate - 1),
|
|
5
|
+
shouldBackOff: back_off && !wasSkipped,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
if (!wasSkipped && errorCount === 0 && hasPendingWork) {
|
|
9
|
+
return { rate: Math.min(max_rpi, rate + 1), shouldBackOff: false };
|
|
10
|
+
}
|
|
11
|
+
return { rate, shouldBackOff: false };
|
|
12
|
+
};
|
|
13
|
+
export function createRateController(options, strategy = linearRateStrategy) {
|
|
14
|
+
let rate = Math.ceil((options.max_rpi + options.min_rpi) / 2);
|
|
15
|
+
let errorCount = 0;
|
|
16
|
+
return {
|
|
17
|
+
get rate() {
|
|
18
|
+
return rate;
|
|
19
|
+
},
|
|
20
|
+
recordCompletion(result) {
|
|
21
|
+
if (result === false)
|
|
22
|
+
errorCount++;
|
|
23
|
+
},
|
|
24
|
+
observe(observation) {
|
|
25
|
+
const decision = strategy({ ...options, ...observation, rate, errorCount });
|
|
26
|
+
rate = Math.min(options.max_rpi, Math.max(options.min_rpi, decision.rate));
|
|
27
|
+
errorCount = 0;
|
|
28
|
+
return { ...decision, rate };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=rate-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-controller.js","sourceRoot":"","sources":["../src/rate-controller.ts"],"names":[],"mappings":"AA4BA,MAAM,kBAAkB,GAAiB,CAAC,EACxC,OAAO,EACP,OAAO,EACP,mBAAmB,EACnB,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,cAAc,EACd,UAAU,GACX,EAAE,EAAE;IACH,IAAI,UAAU,IAAI,mBAAmB,EAAE,CAAC;QACtC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC;YACjC,aAAa,EAAE,QAAQ,IAAI,CAAC,UAAU;SACvC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,CAAC,IAAI,cAAc,EAAE,CAAC;QACtD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IACrE,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACxC,CAAC,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAAC,OAA8B,EAAE,QAAQ,GAAG,kBAAkB;IAChG,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,OAAO;QACL,IAAI,IAAI;YACN,OAAO,IAAI,CAAC;QACd,CAAC;QACD,gBAAgB,CAAC,MAAsB;YACrC,IAAI,MAAM,KAAK,KAAK;gBAAE,UAAU,EAAE,CAAC;QACrC,CAAC;QACD,OAAO,CAAC,WAAwB;YAC9B,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YAC5E,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3E,UAAU,GAAG,CAAC,CAAC;YACf,OAAO,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC/B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
export function createScheduler(options, rateController) {
|
|
2
|
+
const { interval, evenly_spaced = true, retry = 0, concurrency, compact_threshold = 512, onRateChange, } = options;
|
|
3
|
+
let current_rpi = rateController.rate;
|
|
4
|
+
let dyn_interval = evenly_spaced ? interval / current_rpi : interval;
|
|
5
|
+
let dyn_requests_per_interval = evenly_spaced ? 1 : current_rpi;
|
|
6
|
+
let skippedLast = false;
|
|
7
|
+
let isRunning = false;
|
|
8
|
+
let last_called = 0;
|
|
9
|
+
let timeout;
|
|
10
|
+
let dynTimeout;
|
|
11
|
+
let active_count = 0;
|
|
12
|
+
let isStopped = false;
|
|
13
|
+
const max_concurrency = concurrency ?? Infinity;
|
|
14
|
+
const queue = [];
|
|
15
|
+
let head = 0;
|
|
16
|
+
function halt() {
|
|
17
|
+
isRunning = false;
|
|
18
|
+
skippedLast = false;
|
|
19
|
+
clearTimeout(timeout);
|
|
20
|
+
timeout = undefined;
|
|
21
|
+
clearTimeout(dynTimeout);
|
|
22
|
+
dynTimeout = undefined;
|
|
23
|
+
if (head >= queue.length) {
|
|
24
|
+
queue.length = 0;
|
|
25
|
+
head = 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function stop() {
|
|
29
|
+
isStopped = true;
|
|
30
|
+
halt();
|
|
31
|
+
}
|
|
32
|
+
function handleResult(item, result) {
|
|
33
|
+
rateController.recordCompletion(result);
|
|
34
|
+
if (result === false && item.retries > 0) {
|
|
35
|
+
queue.push({ fn: item.fn, retries: item.retries - 1 });
|
|
36
|
+
if (!isRunning && !isStopped && queue.length > head)
|
|
37
|
+
start();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function handleSettlement(item, result, resume = false) {
|
|
41
|
+
active_count--;
|
|
42
|
+
handleResult(item, result);
|
|
43
|
+
if (resume && isRunning && !skippedLast && queue.length > head)
|
|
44
|
+
dequeue();
|
|
45
|
+
}
|
|
46
|
+
function dequeue() {
|
|
47
|
+
const threshold = last_called + dyn_interval;
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
if (now < threshold) {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
timeout = setTimeout(dequeue, threshold - now);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const end = Math.min(head + dyn_requests_per_interval, queue.length);
|
|
55
|
+
let started = 0;
|
|
56
|
+
while (head < end && active_count < max_concurrency) {
|
|
57
|
+
const item = queue[head++];
|
|
58
|
+
active_count++;
|
|
59
|
+
if (started++ === 0)
|
|
60
|
+
last_called = Date.now();
|
|
61
|
+
let result;
|
|
62
|
+
try {
|
|
63
|
+
result = item.fn();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
handleSettlement(item, false);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (result instanceof Promise) {
|
|
70
|
+
void result.then(value => handleSettlement(item, value, true), () => handleSettlement(item, false, true));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
handleSettlement(item, result);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (head > compact_threshold && head > queue.length / 2) {
|
|
77
|
+
queue.splice(0, head);
|
|
78
|
+
head = 0;
|
|
79
|
+
}
|
|
80
|
+
if (head >= queue.length) {
|
|
81
|
+
halt();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (active_count >= max_concurrency)
|
|
85
|
+
return;
|
|
86
|
+
timeout = setTimeout(dequeue, dyn_interval);
|
|
87
|
+
}
|
|
88
|
+
function applyRate(newRpi) {
|
|
89
|
+
if (newRpi === current_rpi)
|
|
90
|
+
return;
|
|
91
|
+
current_rpi = newRpi;
|
|
92
|
+
onRateChange?.(current_rpi);
|
|
93
|
+
if (evenly_spaced)
|
|
94
|
+
dyn_interval = interval / current_rpi;
|
|
95
|
+
else
|
|
96
|
+
dyn_requests_per_interval = current_rpi;
|
|
97
|
+
}
|
|
98
|
+
function adjustRate() {
|
|
99
|
+
dynTimeout = undefined;
|
|
100
|
+
const wasSkipped = skippedLast;
|
|
101
|
+
skippedLast = false;
|
|
102
|
+
const decision = rateController.observe({ hasPendingWork: queue.length > head, wasSkipped });
|
|
103
|
+
applyRate(decision.rate);
|
|
104
|
+
if (decision.shouldBackOff) {
|
|
105
|
+
clearTimeout(timeout);
|
|
106
|
+
skippedLast = true;
|
|
107
|
+
timeout = setTimeout(dequeue, dyn_interval + interval);
|
|
108
|
+
}
|
|
109
|
+
if (isRunning)
|
|
110
|
+
dynTimeout = setTimeout(adjustRate, interval);
|
|
111
|
+
}
|
|
112
|
+
function start() {
|
|
113
|
+
if (skippedLast)
|
|
114
|
+
return;
|
|
115
|
+
isRunning = true;
|
|
116
|
+
last_called = Date.now();
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
timeout = setTimeout(dequeue, dyn_interval);
|
|
119
|
+
if (!dynTimeout)
|
|
120
|
+
dynTimeout = setTimeout(adjustRate, interval);
|
|
121
|
+
}
|
|
122
|
+
function enqueue(callback) {
|
|
123
|
+
queue.push({ fn: callback, retries: retry });
|
|
124
|
+
isStopped = false;
|
|
125
|
+
if (!isRunning)
|
|
126
|
+
start();
|
|
127
|
+
}
|
|
128
|
+
enqueue.stop = stop;
|
|
129
|
+
Object.defineProperty(enqueue, "pending", { get: () => queue.length - head });
|
|
130
|
+
return enqueue;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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.1.
|
|
3
|
+
"version": "2.1.1-rc.6f2d27a",
|
|
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": [
|