@zingage/scheduling 1.1.0 → 1.2.0
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 +184 -5
- package/dist/batcher.d.ts +231 -0
- package/dist/batcher.d.ts.map +1 -0
- package/dist/batcher.js +391 -0
- package/dist/batcher.js.map +1 -0
- package/dist/collapsed-task-creator.d.ts +235 -0
- package/dist/collapsed-task-creator.d.ts.map +1 -0
- package/dist/collapsed-task-creator.js +491 -0
- package/dist/collapsed-task-creator.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +16 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/pending-promises-tracker.d.ts +32 -0
- package/dist/pending-promises-tracker.d.ts.map +1 -0
- package/dist/pending-promises-tracker.js +60 -0
- package/dist/pending-promises-tracker.js.map +1 -0
- package/dist/performance-now.d.ts +14 -0
- package/dist/performance-now.d.ts.map +1 -0
- package/dist/performance-now.js +10 -0
- package/dist/performance-now.js.map +1 -0
- package/dist/scheduling-contract-error.d.ts +9 -0
- package/dist/scheduling-contract-error.d.ts.map +1 -0
- package/dist/scheduling-contract-error.js +9 -0
- package/dist/scheduling-contract-error.js.map +1 -0
- package/dist/wrap-with-task-collapsing.d.ts +30 -0
- package/dist/wrap-with-task-collapsing.d.ts.map +1 -0
- package/dist/wrap-with-task-collapsing.js +80 -0
- package/dist/wrap-with-task-collapsing.js.map +1 -0
- package/package.json +10 -4
- package/src/batcher.ts +614 -0
- package/src/collapsed-task-creator.ts +774 -0
- package/src/index.ts +22 -0
- package/src/logger.ts +32 -0
- package/src/pending-promises-tracker.ts +62 -0
- package/src/performance-now.ts +21 -0
- package/src/scheduling-contract-error.ts +8 -0
- package/src/wrap-with-task-collapsing.ts +103 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { partition } from "es-toolkit";
|
|
2
|
+
import { SchedulingContractError } from "./scheduling-contract-error.js";
|
|
3
|
+
/**
|
|
4
|
+
* A task that its `schedule` call has scheduled. Only these tasks are in
|
|
5
|
+
* `#latestPendingTaskByKey` and `#live`, so a failed `schedule` call leaves no
|
|
6
|
+
* task behind.
|
|
7
|
+
*/
|
|
8
|
+
class Task {
|
|
9
|
+
/** The parts of the task that existed before its `schedule` call returned. */
|
|
10
|
+
start;
|
|
11
|
+
/**
|
|
12
|
+
* The handle that the task's `schedule` call returned. `startOrRideTasks`
|
|
13
|
+
* returns it to every request on the task.
|
|
14
|
+
*/
|
|
15
|
+
handle;
|
|
16
|
+
/** The result that the task's `schedule` call returned. */
|
|
17
|
+
result;
|
|
18
|
+
/**
|
|
19
|
+
* Undefined once the task can never be cancelled: from the start when its
|
|
20
|
+
* starting request has no signal, after a later request without a signal
|
|
21
|
+
* (see `SharedTaskView.signal`), or once the task settles. In the last two
|
|
22
|
+
* cases, the task keeps the signal that `schedule` already received, but
|
|
23
|
+
* nothing can abort it any more.
|
|
24
|
+
*/
|
|
25
|
+
#cancellation;
|
|
26
|
+
/**
|
|
27
|
+
* @param controller The controller whose signal is `start.signal`, or
|
|
28
|
+
* undefined when the starting request has no signal.
|
|
29
|
+
* @param onSettle Called once, when `result` settles, after the task has
|
|
30
|
+
* become uncancellable and removed its abort listeners.
|
|
31
|
+
*/
|
|
32
|
+
constructor(start, controller, { result, handle }, onSettle) {
|
|
33
|
+
this.start = start;
|
|
34
|
+
this.#cancellation = controller && {
|
|
35
|
+
controller,
|
|
36
|
+
uncancelledRequests: 0,
|
|
37
|
+
lastCancelReason: undefined,
|
|
38
|
+
abortListeners: [],
|
|
39
|
+
};
|
|
40
|
+
this.handle = handle;
|
|
41
|
+
this.result = result;
|
|
42
|
+
// Handling both outcomes also marks a rejection as handled: every task
|
|
43
|
+
// request may be cancelled before anyone awaits the result.
|
|
44
|
+
const settle = () => {
|
|
45
|
+
this.#makeUncancelable();
|
|
46
|
+
onSettle();
|
|
47
|
+
};
|
|
48
|
+
// oxlint-disable-next-line no-void -- Runs settle bookkeeping; callers await the result themselves.
|
|
49
|
+
void result.then(settle, settle);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Keeps the task's signal from aborting until `release`, even if every
|
|
53
|
+
* request on the task is cancelled meanwhile. `startOrRideTasks` holds each
|
|
54
|
+
* task it starts or rides until it has recorded all of its requests, so a
|
|
55
|
+
* cancellation during `schedule`, or during another task's abort, cannot
|
|
56
|
+
* cancel a task that a request of the call will keep alive.
|
|
57
|
+
*
|
|
58
|
+
* A hold counts as one uncancelled request. Holds can nest, because a
|
|
59
|
+
* nested `startOrRideTasks` call can ride a task that an outer call holds.
|
|
60
|
+
* It is inert on an uncancellable task, which has no signal to abort.
|
|
61
|
+
*/
|
|
62
|
+
hold() {
|
|
63
|
+
if (this.#cancellation)
|
|
64
|
+
this.#cancellation.uncancelledRequests += 1;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Ends one `hold`. Aborts the task's signal if no request keeps the task
|
|
68
|
+
* alive. Like `hold`, it is inert on an uncancellable task.
|
|
69
|
+
*/
|
|
70
|
+
release() {
|
|
71
|
+
const cancellation = this.#cancellation;
|
|
72
|
+
if (cancellation === undefined)
|
|
73
|
+
return;
|
|
74
|
+
cancellation.uncancelledRequests -= 1;
|
|
75
|
+
if (cancellation.uncancelledRequests === 0) {
|
|
76
|
+
cancellation.controller.abort(cancellation.lastCancelReason);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Records a task request on this task, whether the request started the
|
|
81
|
+
* task or rides it. `signal` and `rode` are the request's. Call it only
|
|
82
|
+
* while holding the task: a request whose signal already aborted is not
|
|
83
|
+
* counted, so only `release` can abort the task for it.
|
|
84
|
+
*/
|
|
85
|
+
addRequest(signal, rode) {
|
|
86
|
+
if (rode)
|
|
87
|
+
this.start.collapsedRequestCount += 1;
|
|
88
|
+
// An uncancellable task needs no count: no task request can cancel it.
|
|
89
|
+
const cancellation = this.#cancellation;
|
|
90
|
+
if (cancellation === undefined)
|
|
91
|
+
return;
|
|
92
|
+
// A request w/o a signal can never be cancelled, so neither can the task.
|
|
93
|
+
if (signal === undefined) {
|
|
94
|
+
this.#makeUncancelable();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// `schedule` runs user code, so a signal can abort after `startOrRideTasks`
|
|
98
|
+
// checked it.
|
|
99
|
+
if (signal.aborted) {
|
|
100
|
+
cancellation.lastCancelReason = signal.reason;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
cancellation.uncancelledRequests += 1;
|
|
104
|
+
const listener = () => {
|
|
105
|
+
cancellation.lastCancelReason = signal.reason;
|
|
106
|
+
this.release();
|
|
107
|
+
};
|
|
108
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
109
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- The task owns its listener list, and removes every listener later.
|
|
110
|
+
cancellation.abortListeners.push([signal, listener]);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Clears `#cancellation`, and removes its abort listeners, which can no
|
|
114
|
+
* longer abort the task. Calling it again does nothing.
|
|
115
|
+
*/
|
|
116
|
+
#makeUncancelable() {
|
|
117
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Removes each listener for its side effect.
|
|
118
|
+
this.#cancellation?.abortListeners.forEach(([signal, listener]) => {
|
|
119
|
+
signal.removeEventListener("abort", listener);
|
|
120
|
+
});
|
|
121
|
+
this.#cancellation = undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Builds a task's start, and the controller that aborts its signal. The
|
|
126
|
+
* controller stays out of the start because `schedule` receives the start,
|
|
127
|
+
* and only the task may abort its own signal.
|
|
128
|
+
*/
|
|
129
|
+
function newTaskStart(input, cancellable, now, startReason) {
|
|
130
|
+
const controller = cancellable ? new AbortController() : undefined;
|
|
131
|
+
return {
|
|
132
|
+
start: {
|
|
133
|
+
startReason,
|
|
134
|
+
startedAt: now,
|
|
135
|
+
input,
|
|
136
|
+
signal: controller?.signal,
|
|
137
|
+
collapsedRequestCount: 0,
|
|
138
|
+
},
|
|
139
|
+
controller,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Lets requests for the same work share one pending task, with optional
|
|
144
|
+
* cancellation.
|
|
145
|
+
*
|
|
146
|
+
* Imagine you have a function that, when called, kicks off a task, and
|
|
147
|
+
* returns a promise representing the result of the task. Now, suppose the task
|
|
148
|
+
* is expensive, but not effectful, like fetching from a REST API. If your code
|
|
149
|
+
* attempts to run this task many times in quick succession, or many times in
|
|
150
|
+
* parallel, this can be wasteful (if the result will always or likely be the
|
|
151
|
+
* same) and/or overload whatever's doing work as part of the task (eg the API).
|
|
152
|
+
*
|
|
153
|
+
* There are a large number of npm packages that make it easy to better schedule
|
|
154
|
+
* this work (e.g., queueing some parallel runs of the task behind others, if
|
|
155
|
+
* the number of tasks already running exceeds some concurrency limit) or to
|
|
156
|
+
* reuse the results of some runs of the task for others (by debouncing or
|
|
157
|
+
* throttling calls to the function that starts the task). However, none of them
|
|
158
|
+
* have quite the -- admittedly a bit quirky -- semantics that we want, and that
|
|
159
|
+
* we implement here.
|
|
160
|
+
*
|
|
161
|
+
* Specifically, a task request reuses a pending task, instead of starting a
|
|
162
|
+
* new one, if and only if: 1) its input has the same key as the task's input
|
|
163
|
+
* [a la memoize]; 2) the task started no more than `collapseTasksMs` ago [a la
|
|
164
|
+
* throttle]; and 3) the task is still running [unique], and is not cancelled.
|
|
165
|
+
* In other words, it memoizes task creation, but then reverts to starting a
|
|
166
|
+
* new task after a certain number of milliseconds or after the last-started
|
|
167
|
+
* task finishes, whichever is first. So it's sort of a combination of memoize,
|
|
168
|
+
* throttle, and promise chaining.
|
|
169
|
+
*
|
|
170
|
+
* Beyond that collapsing, it adds three things:
|
|
171
|
+
*
|
|
172
|
+
* - One `startOrRideTasks` call takes many task requests. Each task request
|
|
173
|
+
* either starts a new task or rides a pending task for its input's key. The
|
|
174
|
+
* tasks that one call starts are scheduled together, in one `schedule` call,
|
|
175
|
+
* so a bulk caller's new inputs become one unit of work. Task requests that
|
|
176
|
+
* ride start nothing.
|
|
177
|
+
* - Each task has an optional AbortSignal that aborts when every request on the
|
|
178
|
+
* task is cancelled. `schedule` passes it to whatever runs the work, such as
|
|
179
|
+
* a batcher's queue or a producer that supports cancellation. See
|
|
180
|
+
* `SharedTaskView.signal`.
|
|
181
|
+
* - It counts the task requests that ride each task, for diagnostics.
|
|
182
|
+
*
|
|
183
|
+
* For the simple case, with one call per task request and no cancellation,
|
|
184
|
+
* `wrapWithTaskCollapsing` wraps a task-starting function with this class.
|
|
185
|
+
*
|
|
186
|
+
* Terms:
|
|
187
|
+
* - An input is the value that a task's work needs, such as a producer request.
|
|
188
|
+
* `makeKey` derives its key.
|
|
189
|
+
* - A task request is one input plus an optional signal that cancels the task
|
|
190
|
+
* request. It is one item of `startOrRideTasks`'s `taskRequests`.
|
|
191
|
+
* - A task request starts a task or rides one. A task's requests are the task
|
|
192
|
+
* request that started it and every task request that rides it.
|
|
193
|
+
* - A task request is cancelled when its signal aborts. A task is cancelled
|
|
194
|
+
* when all of its requests are cancelled.
|
|
195
|
+
*
|
|
196
|
+
* The creator itself does not queue, batch or limit work. The `schedule`
|
|
197
|
+
* callback owns that.
|
|
198
|
+
*
|
|
199
|
+
* A task stops being ridable when it settles, when it is cancelled, or when it
|
|
200
|
+
* started more than `collapseTasksMs` ago. A cancelled task can still be
|
|
201
|
+
* running, but a new task request must not ride work that is being cancelled.
|
|
202
|
+
*/
|
|
203
|
+
export class CollapsedTaskCreator {
|
|
204
|
+
#collapseTasksMs;
|
|
205
|
+
#makeKey;
|
|
206
|
+
#schedule;
|
|
207
|
+
/**
|
|
208
|
+
* For each key, the newest pending task: the task that a new task request
|
|
209
|
+
* for that key rides, unless it is too old or cancelled. A cancelled task
|
|
210
|
+
* stays here until it settles or a newer task replaces it; the lookup in
|
|
211
|
+
* `startOrRideTasks` checks its signal, so no request rides it. A newer
|
|
212
|
+
* task replaces an older one whose start is outside `collapseTasksMs`, or
|
|
213
|
+
* a cancelled one; the replaced task keeps running, tracked only in
|
|
214
|
+
* `#live`.
|
|
215
|
+
*/
|
|
216
|
+
#latestPendingTaskByKey = new Map();
|
|
217
|
+
/**
|
|
218
|
+
* Every task that has not settled. `#latestPendingTaskByKey` leaves out two
|
|
219
|
+
* kinds of unsettled task: tasks with no key, and tasks that a newer task
|
|
220
|
+
* replaced. `drain` must still wait for all of them.
|
|
221
|
+
*/
|
|
222
|
+
#live = new Set();
|
|
223
|
+
/**
|
|
224
|
+
* The keys of the tasks that a running `schedule` call is starting. Those
|
|
225
|
+
* tasks enter `#latestPendingTaskByKey` only after `schedule` returns. So
|
|
226
|
+
* when `schedule` runs a producer synchronously, and that producer requests
|
|
227
|
+
* its own key, a nested call could not ride the task and would start it
|
|
228
|
+
* again, without end. The nested call throws instead.
|
|
229
|
+
*
|
|
230
|
+
* Tasks with no key are never reserved, because nothing can ride them. A
|
|
231
|
+
* key stays here only while its `schedule` call runs. The set holds keys
|
|
232
|
+
* from several calls only while `schedule` calls nest, with different keys.
|
|
233
|
+
*/
|
|
234
|
+
#schedulingKeys = new Set();
|
|
235
|
+
constructor(options) {
|
|
236
|
+
this.#collapseTasksMs = options.collapseTasksMs;
|
|
237
|
+
this.#makeKey = options.makeKey;
|
|
238
|
+
this.#schedule = options.schedule;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Finds the pending task that a task request for `key` may ride, or says
|
|
242
|
+
* why there is none. A task that is both too old and cancelled reports
|
|
243
|
+
* `pendingTaskTooOld`.
|
|
244
|
+
*/
|
|
245
|
+
#findRidable(now, key) {
|
|
246
|
+
const existing = this.#latestPendingTaskByKey.get(key);
|
|
247
|
+
if (!existing)
|
|
248
|
+
return { startReason: "noPendingTask" };
|
|
249
|
+
const { signal, startedAt } = existing.start;
|
|
250
|
+
if (now - startedAt > this.#collapseTasksMs) {
|
|
251
|
+
return { startReason: "pendingTaskTooOld" };
|
|
252
|
+
}
|
|
253
|
+
if (signal?.aborted)
|
|
254
|
+
return { startReason: "pendingTaskCancelled" };
|
|
255
|
+
return { ride: existing };
|
|
256
|
+
}
|
|
257
|
+
#setupNewTask(start, controller, scheduleResult, key) {
|
|
258
|
+
const task = new Task(start, controller, scheduleResult, () => {
|
|
259
|
+
this.#live.delete(task);
|
|
260
|
+
// Only remove this task if the map still holds it for its key. The map
|
|
261
|
+
// could hold a newer one if this task was replaced for being too old or
|
|
262
|
+
// cancelled.
|
|
263
|
+
if (key !== undefined && this.#latestPendingTaskByKey.get(key) === task) {
|
|
264
|
+
this.#latestPendingTaskByKey.delete(key);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
this.#live.add(task);
|
|
268
|
+
// If the key is defined, this task can be the existing pending task
|
|
269
|
+
// for future requests with the same key, so register it.
|
|
270
|
+
if (key !== undefined) {
|
|
271
|
+
this.#latestPendingTaskByKey.set(key, task);
|
|
272
|
+
}
|
|
273
|
+
return task;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Each task request rides a pending task for its input's key, or starts a
|
|
277
|
+
* new task. Schedules the new tasks with one `schedule` call. When every
|
|
278
|
+
* task request rides, it does not call `schedule`.
|
|
279
|
+
*
|
|
280
|
+
* A task request whose signal is already aborted, or aborts during
|
|
281
|
+
* `makeKey`, starts and rides nothing. Its `result` rejects with the
|
|
282
|
+
* signal's reason, and the call's other task requests are not affected.
|
|
283
|
+
*
|
|
284
|
+
* It changes no state until `schedule` returns, except that it holds the
|
|
285
|
+
* tasks it rides. So when `makeKey` throws, `schedule` throws, or
|
|
286
|
+
* `schedule` returns the wrong number of entries, it releases those tasks,
|
|
287
|
+
* throws, and changes nothing else. It also holds
|
|
288
|
+
* each task it starts until it has recorded all of its task requests (see
|
|
289
|
+
* `Task.hold`).
|
|
290
|
+
*
|
|
291
|
+
* The tasks this call starts become ridable only after `schedule`
|
|
292
|
+
* returns. A `schedule` may call `startOrRideTasks` again, for example by
|
|
293
|
+
* running a producer that reads other keys. But a nested call that
|
|
294
|
+
* requests a key this call is starting throws a `SchedulingContractError`:
|
|
295
|
+
* that request depends on its own result.
|
|
296
|
+
*
|
|
297
|
+
* @param taskRequests Each task request's input, and an optional signal
|
|
298
|
+
* that cancels the task request.
|
|
299
|
+
* @param context Passed unchanged to `schedule`, if this call starts any
|
|
300
|
+
* task. A nested call passes its own context. A creator whose `schedule`
|
|
301
|
+
* needs none keeps the default `Context`, `void`, so its callers omit
|
|
302
|
+
* it. TypeScript lets a caller omit a trailing argument only when its
|
|
303
|
+
* type accepts `void`; `undefined` does not count.
|
|
304
|
+
* @returns One entry per task request, in order. `result` is the task's
|
|
305
|
+
* shared result. It does not reject when only this task request is
|
|
306
|
+
* cancelled, because the task's other requests may still need the
|
|
307
|
+
* result; a caller races its own signal. `handle` is the handle that the
|
|
308
|
+
* task's `schedule` returned. A caller that wants its work run at once
|
|
309
|
+
* can dispatch the still-queued tasks behind these handles, including
|
|
310
|
+
* the tasks it rode. It is undefined for a cancelled task request.
|
|
311
|
+
* `rode` is true when the task request rode a task that it did not
|
|
312
|
+
* start.
|
|
313
|
+
*/
|
|
314
|
+
startOrRideTasks(taskRequests, context) {
|
|
315
|
+
// A cancelled task request needs no key. Signals are read again after
|
|
316
|
+
// every key: `makeKey` is user code and may abort a signal.
|
|
317
|
+
const keys = taskRequests.map(({ input, signal }) => signal?.aborted ? undefined : this.#makeKey(input));
|
|
318
|
+
// A cancelled task request starts and rides nothing, and gets only its
|
|
319
|
+
// own rejection. The split happens once, here, so that a later abort
|
|
320
|
+
// cannot change which requests this call treats as cancelled.
|
|
321
|
+
const [cancelled, live] = partition(taskRequests.map((request, i) => ({ request, i, key: keys[i] })), ({ request: { signal } }) => signal?.aborted === true);
|
|
322
|
+
if (live.some(({ key }) => key !== undefined && this.#schedulingKeys.has(key))) {
|
|
323
|
+
throw new SchedulingContractError("schedule requested a key that it is starting a task for");
|
|
324
|
+
}
|
|
325
|
+
// Group. Requests with the same key share one task, so each group
|
|
326
|
+
// is one task that this call touches.
|
|
327
|
+
const requestsByKey = Map.groupBy(live, ({ key }) => key);
|
|
328
|
+
// Decide. Each group rides the pending task for its key, or starts
|
|
329
|
+
// a new task. The group's first request decides, as if it came alone.
|
|
330
|
+
const now = Date.now();
|
|
331
|
+
const decisions = requestsByKey
|
|
332
|
+
.entries()
|
|
333
|
+
.flatMap(([key, reqs]) => {
|
|
334
|
+
if (key === undefined) {
|
|
335
|
+
return reqs.map(({ request: { input, signal }, i }) => ({
|
|
336
|
+
type: "START",
|
|
337
|
+
key: undefined,
|
|
338
|
+
...newTaskStart(input, signal !== undefined, now, "noKey"),
|
|
339
|
+
indexes: [i],
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
const ridableLookup = this.#findRidable(now, key);
|
|
343
|
+
if ("ride" in ridableLookup) {
|
|
344
|
+
return [
|
|
345
|
+
{
|
|
346
|
+
type: "RIDE",
|
|
347
|
+
task: ridableLookup.ride,
|
|
348
|
+
indexes: reqs.map(({ i }) => i),
|
|
349
|
+
},
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- Map.groupBy never creates an empty group.
|
|
353
|
+
const { input, signal } = reqs[0].request;
|
|
354
|
+
return [
|
|
355
|
+
{
|
|
356
|
+
type: "START",
|
|
357
|
+
key,
|
|
358
|
+
...newTaskStart(input, signal !== undefined, now, ridableLookup.startReason),
|
|
359
|
+
indexes: reqs.map(({ i }) => i),
|
|
360
|
+
},
|
|
361
|
+
];
|
|
362
|
+
})
|
|
363
|
+
// Map.groupBy puts every request with no key in one group, at the
|
|
364
|
+
// position of the first one. Sort by first request, so that `schedule`
|
|
365
|
+
// receives the started tasks in task request order.
|
|
366
|
+
.toArray()
|
|
367
|
+
.toSorted((a, b) => a.indexes[0] - b.indexes[0]);
|
|
368
|
+
// Schedule the new tasks.
|
|
369
|
+
const startDecisions = decisions.filter((d) => d.type === "START");
|
|
370
|
+
const taskStarts = startDecisions.map((d) => d.start);
|
|
371
|
+
// Every task this call touches, held as `Task.hold` describes: the
|
|
372
|
+
// ridden tasks now, and each started task when its requests are
|
|
373
|
+
// recorded. All releases wait until the end, because a release can abort
|
|
374
|
+
// a task and run user code.
|
|
375
|
+
const heldTasks = new Set(decisions.filter((d) => d.type === "RIDE").map((d) => d.task));
|
|
376
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Holds each task for its side effect.
|
|
377
|
+
heldTasks.forEach((task) => task.hold());
|
|
378
|
+
try {
|
|
379
|
+
// Reserve keys for the tasks we're about to schedule (to prevent
|
|
380
|
+
// infinite loop on re-entrant calls).
|
|
381
|
+
const scheduled = (() => {
|
|
382
|
+
if (taskStarts.length === 0) {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
const scheduledKeys = new Set(startDecisions.map((d) => d.key).filter((key) => key !== undefined));
|
|
386
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Reserves each key for its side effect.
|
|
387
|
+
scheduledKeys.forEach((key) => this.#schedulingKeys.add(key));
|
|
388
|
+
try {
|
|
389
|
+
return this.#schedule(taskStarts, context);
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
// Free the keys before any release: a release can abort a
|
|
393
|
+
// task, and its abort listeners may request those keys, which are
|
|
394
|
+
// ridable by then.
|
|
395
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Frees each key for its side effect.
|
|
396
|
+
scheduledKeys.forEach((key) => this.#schedulingKeys.delete(key));
|
|
397
|
+
}
|
|
398
|
+
})();
|
|
399
|
+
if (scheduled.length !== taskStarts.length) {
|
|
400
|
+
throw new SchedulingContractError("schedule must return one scheduled task per task to start");
|
|
401
|
+
}
|
|
402
|
+
// Create a Task for each new task, and set up the initial bookkeeping
|
|
403
|
+
// for it. The map lets the record loop below, which walks RIDE and
|
|
404
|
+
// START decisions interleaved, find each START's new task.
|
|
405
|
+
const newTasksByStartDecision = new Map(startDecisions.map((startDecision, i) => {
|
|
406
|
+
const { start, controller, key } = startDecision;
|
|
407
|
+
const task = this.#setupNewTask(start, controller,
|
|
408
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- The length check above guarantees one entry per start decision.
|
|
409
|
+
scheduled[i], key);
|
|
410
|
+
return [start, task];
|
|
411
|
+
}));
|
|
412
|
+
// Record every request on its task, and build one entry per
|
|
413
|
+
// request, back in request order. Every request in a ridden group
|
|
414
|
+
// rode; in a started group, only the first did not.
|
|
415
|
+
const result = Array(taskRequests.length);
|
|
416
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Fills each cancelled request's result slot.
|
|
417
|
+
cancelled.forEach(({ request: { signal }, i }) => {
|
|
418
|
+
// An aborted signal's reason never changes.
|
|
419
|
+
// oxlint-disable-next-line typescript/prefer-promise-reject-errors -- Rejects with the signal's abort reason, which can be any value.
|
|
420
|
+
const rejected = Promise.reject(signal?.reason);
|
|
421
|
+
// A caller may never await it.
|
|
422
|
+
// oxlint-disable-next-line no-void -- Marks the rejection handled; the caller still gets the rejected promise.
|
|
423
|
+
void rejected.catch(() => { });
|
|
424
|
+
result[i] = { result: rejected, handle: undefined, rode: false };
|
|
425
|
+
});
|
|
426
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Records each decision's requests for their side effects.
|
|
427
|
+
decisions.forEach((d) => {
|
|
428
|
+
switch (d.type) {
|
|
429
|
+
case "RIDE": {
|
|
430
|
+
const outcome = {
|
|
431
|
+
result: d.task.result,
|
|
432
|
+
handle: d.task.handle,
|
|
433
|
+
rode: true,
|
|
434
|
+
};
|
|
435
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Records each request on its task for its side effect.
|
|
436
|
+
d.indexes.forEach((reqIdx) => {
|
|
437
|
+
// NB: task is still held from before schedule()
|
|
438
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- Each index comes from taskRequests.
|
|
439
|
+
d.task.addRequest(taskRequests[reqIdx].signal, true);
|
|
440
|
+
result[reqIdx] = outcome;
|
|
441
|
+
});
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case "START": {
|
|
445
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- The map holds a task for every start decision.
|
|
446
|
+
const task = newTasksByStartDecision.get(d.start);
|
|
447
|
+
// See `heldTasks`. Redundant when the key is undefined: that task
|
|
448
|
+
// has exactly one request, and nothing can ride it.
|
|
449
|
+
task.hold();
|
|
450
|
+
heldTasks.add(task);
|
|
451
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Records each request on its task for its side effect.
|
|
452
|
+
d.indexes.forEach((reqIdx, i) => {
|
|
453
|
+
const rode = i > 0;
|
|
454
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- Each index comes from taskRequests.
|
|
455
|
+
task.addRequest(taskRequests[reqIdx].signal, rode);
|
|
456
|
+
result[reqIdx] = {
|
|
457
|
+
result: task.result,
|
|
458
|
+
handle: task.handle,
|
|
459
|
+
rode,
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
default:
|
|
465
|
+
throw new Error(`Unexpected decision: ${String(d)}`);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
// The loop above fills every position of `result`, one per task
|
|
469
|
+
// request, but TS can't prove that it matches the mapped tuple type.
|
|
470
|
+
return result;
|
|
471
|
+
}
|
|
472
|
+
finally {
|
|
473
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Releases each task for its side effect.
|
|
474
|
+
heldTasks.forEach((task) => task.release());
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Waits until every task has settled, including cancelled ones. It loops
|
|
479
|
+
* because work that settles can start new tasks, for example a producer
|
|
480
|
+
* that reads other keys. Those tasks join `#live` after the snapshot that
|
|
481
|
+
* one `allSettled` waits on.
|
|
482
|
+
*/
|
|
483
|
+
async drain() {
|
|
484
|
+
// oxlint-disable-next-line zingage/no-restricted-syntax -- Loops until no task is live, because settling work can start new tasks.
|
|
485
|
+
while (this.#live.size > 0) {
|
|
486
|
+
// oxlint-disable-next-line typescript/promise-function-async -- Passes the existing promises; an async wrapper adds nothing.
|
|
487
|
+
await Promise.allSettled([...this.#live].map((task) => task.result));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
//# sourceMappingURL=collapsed-task-creator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collapsed-task-creator.js","sourceRoot":"","sources":["../src/collapsed-task-creator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
|
|
2
2
|
export { timeSlicedSort } from "./time-sliced-sort.js";
|
|
3
3
|
export { timeSlicedFind, timeSlicedSome } from "./time-sliced-search.js";
|
|
4
|
+
export { CollapsedTaskCreator, type ScheduleTasks, type SharedTaskView, type TaskRequestOutcome, type TaskRequestOutcomes, type TaskStartReason, } from "./collapsed-task-creator.js";
|
|
5
|
+
export { wrapWithTaskCollapsing } from "./wrap-with-task-collapsing.js";
|
|
6
|
+
export { Batcher, DEFAULT_MAX_BATCH_SIZE, type BatchHandle, type BatchItem, type BatchOptions, type DispatchedItem, type DispatchInfo, type Enqueued, } from "./batcher.js";
|
|
7
|
+
export { PendingPromisesTracker } from "./pending-promises-tracker.js";
|
|
8
|
+
export { SchedulingContractError } from "./scheduling-contract-error.js";
|
|
9
|
+
export type { Logger, SchedulingComponentName } from "./logger.js";
|
|
4
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EACL,OAAO,EACP,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,QAAQ,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,YAAY,EAAE,MAAM,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export { waitFor, timeSlicedFilter, timeSlicedFlatMap, timeSlicedForEach, timeSlicedGroupBy, timeSlicedMap, timeSlicedMapFromEntries, TimeSliceBudget, } from "./time-slicing.js";
|
|
2
2
|
export { timeSlicedSort } from "./time-sliced-sort.js";
|
|
3
3
|
export { timeSlicedFind, timeSlicedSome } from "./time-sliced-search.js";
|
|
4
|
+
export { CollapsedTaskCreator, } from "./collapsed-task-creator.js";
|
|
5
|
+
export { wrapWithTaskCollapsing } from "./wrap-with-task-collapsing.js";
|
|
6
|
+
export { Batcher, DEFAULT_MAX_BATCH_SIZE, } from "./batcher.js";
|
|
7
|
+
export { PendingPromisesTracker } from "./pending-promises-tracker.js";
|
|
8
|
+
export { SchedulingContractError } from "./scheduling-contract-error.js";
|
|
4
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,wBAAwB,EACxB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EACL,oBAAoB,GAMrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EACL,OAAO,EACP,sBAAsB,GAOvB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The components in this package that write log lines. */
|
|
2
|
+
export type SchedulingComponentName = "batcher" | "collapsed-task-creator";
|
|
3
|
+
/**
|
|
4
|
+
* The signature of a custom log function. `Batcher` and
|
|
5
|
+
* `wrapWithTaskCollapsing` accept one, and call it when they log a message.
|
|
6
|
+
*
|
|
7
|
+
* A logger that accepts more component names, such as a logger shared with
|
|
8
|
+
* other packages, is also a valid `Logger`.
|
|
9
|
+
*/
|
|
10
|
+
export type Logger = (component: SchedulingComponentName, level: "trace" | "debug" | "info" | "warn" | "error" | "fatal", message: string, data?: unknown) => void;
|
|
11
|
+
/** The default logger of each component. */
|
|
12
|
+
export declare const defaultLoggersByComponent: {
|
|
13
|
+
batcher: Logger;
|
|
14
|
+
"collapsed-task-creator": Logger;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,2DAA2D;AAC3D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,wBAAwB,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,MAAM,MAAM,GAAG,CACnB,SAAS,EAAE,uBAAuB,EAClC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,EAC9D,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,OAAO,KACX,IAAI,CAAC;AAUV,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB;;;CAGa,CAAC"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { default as debug } from "debug";
|
|
2
|
+
/** A logger that writes to the `debug` namespace `@zingage/scheduling:<name>`. */
|
|
3
|
+
function debugLogger(name) {
|
|
4
|
+
const write = debug(`@zingage/scheduling:${name}`);
|
|
5
|
+
return (_, level, message, data) => {
|
|
6
|
+
write(`(${level}) ${message} %O`, data);
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/** The default logger of each component. */
|
|
10
|
+
export const defaultLoggersByComponent = {
|
|
11
|
+
batcher: debugLogger("batcher"),
|
|
12
|
+
"collapsed-task-creator": debugLogger("collapsed-task-creator"),
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC;AAmBzC,kFAAkF;AAClF,SAAS,WAAW,CAAC,IAA6B;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QACjC,KAAK,CAAC,IAAI,KAAK,KAAK,OAAO,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC;AACJ,CAAC;AAED,4CAA4C;AAC5C,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC;IAC/B,wBAAwB,EAAE,WAAW,CAAC,wBAAwB,CAAC;CACd,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks promises until they settle, so that an owner's `close` can wait for
|
|
3
|
+
* the work it accepted.
|
|
4
|
+
*
|
|
5
|
+
* Each tracked promise leaves the tracker when it settles. Tracking never
|
|
6
|
+
* creates an unhandled rejection: the tracker observes a rejection, but the
|
|
7
|
+
* caller still owns the promise and its rejection.
|
|
8
|
+
*
|
|
9
|
+
* The tracker has no closed state. Each owner decides what new work does
|
|
10
|
+
* after its own `close` starts, and in what order it drains its trackers.
|
|
11
|
+
*/
|
|
12
|
+
export declare class PendingPromisesTracker {
|
|
13
|
+
#private;
|
|
14
|
+
/** The number of tracked promises that have not settled yet. */
|
|
15
|
+
get size(): number;
|
|
16
|
+
/** Tracks `promise` until it settles, and returns it unchanged. */
|
|
17
|
+
track<T>(promise: Promise<T>): Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Calls `start` and tracks its result. The promise is tracked before
|
|
20
|
+
* `start` runs, so a `drain` that `start` itself triggers synchronously
|
|
21
|
+
* still waits for it. A synchronous throw from `start` becomes a rejection.
|
|
22
|
+
*/
|
|
23
|
+
run<T>(start: () => Promise<T>): Promise<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves once no tracked promise is pending, including promises tracked
|
|
26
|
+
* while it waits. It loops because settling work can start more work, for
|
|
27
|
+
* example a producer that reads other keys from its own wrapper. It never
|
|
28
|
+
* rejects.
|
|
29
|
+
*/
|
|
30
|
+
drain(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=pending-promises-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-promises-tracker.d.ts","sourceRoot":"","sources":["../src/pending-promises-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,qBAAa,sBAAsB;;IAGjC,gEAAgE;IAChE,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,mEAAmE;IAEnE,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQxC;IAED;;;;OAIG;IAEH,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAU1C;IAED;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;CACF"}
|