@dmop/puru 0.1.10 → 0.1.12
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/AGENTS.md +137 -2
- package/README.md +124 -164
- package/dist/index.cjs +810 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -8
- package/dist/index.d.ts +341 -8
- package/dist/index.js +798 -48
- package/dist/index.js.map +1 -1
- package/llms-full.txt +175 -8
- package/llms.txt +10 -6
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
var NATIVE_CODE_RE = /\[native code\]/;
|
|
3
3
|
var METHOD_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/;
|
|
4
4
|
var VALID_FN_START_RE = /^(?:function\b|async\s+function\b|async\s*\(|\(|[a-zA-Z_$][a-zA-Z0-9_$]*\s*=>|async\s+[a-zA-Z_$])/;
|
|
5
|
+
var serializeCache = /* @__PURE__ */ new WeakMap();
|
|
5
6
|
function serializeFunction(fn) {
|
|
6
7
|
if (typeof fn !== "function") {
|
|
7
8
|
throw new TypeError("Expected a function");
|
|
8
9
|
}
|
|
10
|
+
const cached = serializeCache.get(fn);
|
|
11
|
+
if (cached) return cached;
|
|
9
12
|
const str = fn.toString();
|
|
10
13
|
if (typeof str !== "string" || str.length === 0) {
|
|
11
14
|
throw new TypeError(
|
|
@@ -27,6 +30,7 @@ function serializeFunction(fn) {
|
|
|
27
30
|
"Class methods cannot be serialized. Use an arrow function wrapper instead."
|
|
28
31
|
);
|
|
29
32
|
}
|
|
33
|
+
serializeCache.set(fn, str);
|
|
30
34
|
return str;
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -147,15 +151,23 @@ function __buildChannelProxies(channels) {
|
|
|
147
151
|
return proxies;
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
|
|
154
|
+
const __fnCache = new Map();
|
|
155
|
+
const __FN_CACHE_MAX = 1000;
|
|
156
|
+
|
|
157
|
+
function __execFn(fnStr, channels, args) {
|
|
158
|
+
let parsedFn = __fnCache.get(fnStr);
|
|
159
|
+
if (!parsedFn) {
|
|
160
|
+
parsedFn = (new Function('return (' + fnStr + ')'))();
|
|
161
|
+
if (__fnCache.size >= __FN_CACHE_MAX) __fnCache.delete(__fnCache.keys().next().value);
|
|
162
|
+
__fnCache.set(fnStr, parsedFn);
|
|
163
|
+
}
|
|
164
|
+
if (args) {
|
|
165
|
+
return parsedFn(...args);
|
|
166
|
+
}
|
|
151
167
|
if (channels) {
|
|
152
|
-
|
|
153
|
-
const fn = new Function('__ch', 'return (' + fnStr + ')(__ch)');
|
|
154
|
-
return fn(__ch);
|
|
155
|
-
} else {
|
|
156
|
-
const fn = new Function('return (' + fnStr + ')()');
|
|
157
|
-
return fn();
|
|
168
|
+
return parsedFn(__buildChannelProxies(channels));
|
|
158
169
|
}
|
|
170
|
+
return parsedFn();
|
|
159
171
|
}
|
|
160
172
|
`;
|
|
161
173
|
var NODE_BOOTSTRAP_CODE = `
|
|
@@ -181,7 +193,7 @@ parentPort.on('message', async (msg) => {
|
|
|
181
193
|
return;
|
|
182
194
|
}
|
|
183
195
|
try {
|
|
184
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
196
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
185
197
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
186
198
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
187
199
|
}
|
|
@@ -200,7 +212,7 @@ parentPort.on('message', async (msg) => {
|
|
|
200
212
|
})();
|
|
201
213
|
} else {
|
|
202
214
|
try {
|
|
203
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
215
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
204
216
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
205
217
|
} catch (error) {
|
|
206
218
|
parentPort.postMessage({
|
|
@@ -241,7 +253,7 @@ self.onmessage = async (event) => {
|
|
|
241
253
|
return;
|
|
242
254
|
}
|
|
243
255
|
try {
|
|
244
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
256
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
245
257
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
246
258
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
247
259
|
}
|
|
@@ -260,7 +272,7 @@ self.onmessage = async (event) => {
|
|
|
260
272
|
})();
|
|
261
273
|
} else {
|
|
262
274
|
try {
|
|
263
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
275
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
264
276
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
265
277
|
} catch (error) {
|
|
266
278
|
self.postMessage({
|
|
@@ -347,17 +359,14 @@ var BunManagedWorker = class {
|
|
|
347
359
|
on(event, handler) {
|
|
348
360
|
if (event === "message") {
|
|
349
361
|
this.worker.addEventListener("message", (e) => {
|
|
350
|
-
;
|
|
351
362
|
handler(e.data);
|
|
352
363
|
});
|
|
353
364
|
} else if (event === "error") {
|
|
354
365
|
this.worker.addEventListener("error", (e) => {
|
|
355
|
-
;
|
|
356
366
|
handler(e.error ?? new Error(e.message));
|
|
357
367
|
});
|
|
358
368
|
} else if (event === "exit") {
|
|
359
369
|
this.worker.addEventListener("close", (e) => {
|
|
360
|
-
;
|
|
361
370
|
handler(e.code ?? 0);
|
|
362
371
|
});
|
|
363
372
|
}
|
|
@@ -387,6 +396,8 @@ var ChannelImpl = class {
|
|
|
387
396
|
// constraint: can't create channels of nullable type
|
|
388
397
|
/** @internal */
|
|
389
398
|
_id;
|
|
399
|
+
/** @internal — true once the channel ID has been sent to a worker */
|
|
400
|
+
_shared = false;
|
|
390
401
|
buffer = [];
|
|
391
402
|
capacity;
|
|
392
403
|
closed = false;
|
|
@@ -397,6 +408,12 @@ var ChannelImpl = class {
|
|
|
397
408
|
this.capacity = capacity;
|
|
398
409
|
channelRegistry.set(this._id, this);
|
|
399
410
|
}
|
|
411
|
+
get len() {
|
|
412
|
+
return this.buffer.length;
|
|
413
|
+
}
|
|
414
|
+
get cap() {
|
|
415
|
+
return this.capacity;
|
|
416
|
+
}
|
|
400
417
|
send(value) {
|
|
401
418
|
if (this.closed) {
|
|
402
419
|
return Promise.reject(new Error("send on closed channel"));
|
|
@@ -422,6 +439,7 @@ var ChannelImpl = class {
|
|
|
422
439
|
this.buffer.push(sender2.value);
|
|
423
440
|
sender2.resolve();
|
|
424
441
|
}
|
|
442
|
+
this.maybeUnregister();
|
|
425
443
|
return Promise.resolve(value);
|
|
426
444
|
}
|
|
427
445
|
const sender = this.sendQueue.shift();
|
|
@@ -430,6 +448,7 @@ var ChannelImpl = class {
|
|
|
430
448
|
return Promise.resolve(sender.value);
|
|
431
449
|
}
|
|
432
450
|
if (this.closed) {
|
|
451
|
+
this.maybeUnregister();
|
|
433
452
|
return Promise.resolve(null);
|
|
434
453
|
}
|
|
435
454
|
return new Promise((resolve) => {
|
|
@@ -450,6 +469,45 @@ var ChannelImpl = class {
|
|
|
450
469
|
}
|
|
451
470
|
this.sendQueue = [];
|
|
452
471
|
}
|
|
472
|
+
maybeUnregister() {
|
|
473
|
+
if (!this._shared && this.closed && this.buffer.length === 0 && this.recvQueue.length === 0) {
|
|
474
|
+
channelRegistry.delete(this._id);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
sendOnly() {
|
|
478
|
+
const send = (value) => this.send(value);
|
|
479
|
+
const close = () => this.close();
|
|
480
|
+
const getLen = () => this.len;
|
|
481
|
+
const getCap = () => this.cap;
|
|
482
|
+
return {
|
|
483
|
+
send,
|
|
484
|
+
close,
|
|
485
|
+
get len() {
|
|
486
|
+
return getLen();
|
|
487
|
+
},
|
|
488
|
+
get cap() {
|
|
489
|
+
return getCap();
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
recvOnly() {
|
|
494
|
+
const recv = () => this.recv();
|
|
495
|
+
const getLen = () => this.len;
|
|
496
|
+
const getCap = () => this.cap;
|
|
497
|
+
const getIter = () => this[Symbol.asyncIterator]();
|
|
498
|
+
return {
|
|
499
|
+
recv,
|
|
500
|
+
get len() {
|
|
501
|
+
return getLen();
|
|
502
|
+
},
|
|
503
|
+
get cap() {
|
|
504
|
+
return getCap();
|
|
505
|
+
},
|
|
506
|
+
[Symbol.asyncIterator]() {
|
|
507
|
+
return getIter();
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
453
511
|
async *[Symbol.asyncIterator]() {
|
|
454
512
|
while (true) {
|
|
455
513
|
const value = await this.recv();
|
|
@@ -468,7 +526,9 @@ function getChannelById(id) {
|
|
|
468
526
|
return channelRegistry.get(id);
|
|
469
527
|
}
|
|
470
528
|
function getChannelId(channel) {
|
|
471
|
-
|
|
529
|
+
const impl = channel;
|
|
530
|
+
impl._shared = true;
|
|
531
|
+
return impl._id;
|
|
472
532
|
}
|
|
473
533
|
|
|
474
534
|
// src/adapters/inline.ts
|
|
@@ -480,6 +540,7 @@ var InlineManagedWorker = class {
|
|
|
480
540
|
exitHandlers = [];
|
|
481
541
|
terminated = false;
|
|
482
542
|
cancelledTasks = /* @__PURE__ */ new Set();
|
|
543
|
+
fnCache = /* @__PURE__ */ new Map();
|
|
483
544
|
constructor() {
|
|
484
545
|
this.id = ++inlineIdCounter;
|
|
485
546
|
queueMicrotask(() => {
|
|
@@ -489,7 +550,7 @@ var InlineManagedWorker = class {
|
|
|
489
550
|
postMessage(msg) {
|
|
490
551
|
if (this.terminated) return;
|
|
491
552
|
if (msg.type === "execute") {
|
|
492
|
-
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels);
|
|
553
|
+
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels, msg.args);
|
|
493
554
|
} else if (msg.type === "cancel") {
|
|
494
555
|
this.cancelledTasks.add(msg.taskId);
|
|
495
556
|
} else if (msg.type === "channel-result") {
|
|
@@ -550,7 +611,7 @@ var InlineManagedWorker = class {
|
|
|
550
611
|
}
|
|
551
612
|
return proxies;
|
|
552
613
|
}
|
|
553
|
-
executeTask(taskId, fnStr, concurrent, channels) {
|
|
614
|
+
executeTask(taskId, fnStr, concurrent, channels, args) {
|
|
554
615
|
queueMicrotask(async () => {
|
|
555
616
|
if (this.terminated) return;
|
|
556
617
|
if (concurrent && this.cancelledTasks.has(taskId)) {
|
|
@@ -558,14 +619,20 @@ var InlineManagedWorker = class {
|
|
|
558
619
|
return;
|
|
559
620
|
}
|
|
560
621
|
try {
|
|
622
|
+
let parsedFn = this.fnCache.get(fnStr);
|
|
623
|
+
if (!parsedFn) {
|
|
624
|
+
parsedFn = new Function("return (" + fnStr + ")")();
|
|
625
|
+
if (this.fnCache.size >= 1e3) this.fnCache.clear();
|
|
626
|
+
this.fnCache.set(fnStr, parsedFn);
|
|
627
|
+
}
|
|
561
628
|
let result;
|
|
562
|
-
if (
|
|
629
|
+
if (args) {
|
|
630
|
+
result = await parsedFn(...args);
|
|
631
|
+
} else if (channels) {
|
|
563
632
|
const proxies = this.buildChannelProxies(channels);
|
|
564
|
-
|
|
565
|
-
result = await fn(proxies);
|
|
633
|
+
result = await parsedFn(proxies);
|
|
566
634
|
} else {
|
|
567
|
-
|
|
568
|
-
result = await fn();
|
|
635
|
+
result = await parsedFn();
|
|
569
636
|
}
|
|
570
637
|
if (concurrent && this.cancelledTasks.has(taskId)) {
|
|
571
638
|
this.cancelledTasks.delete(taskId);
|
|
@@ -618,6 +685,8 @@ var WorkerPool = class {
|
|
|
618
685
|
totalCompleted = 0;
|
|
619
686
|
totalFailed = 0;
|
|
620
687
|
taskMap = /* @__PURE__ */ new Map();
|
|
688
|
+
// Per-worker deques for work-stealing strategy
|
|
689
|
+
workerDeques = /* @__PURE__ */ new Map();
|
|
621
690
|
constructor(config, adapter) {
|
|
622
691
|
this.config = config;
|
|
623
692
|
this.adapter = adapter;
|
|
@@ -655,6 +724,110 @@ var WorkerPool = class {
|
|
|
655
724
|
}
|
|
656
725
|
return void 0;
|
|
657
726
|
}
|
|
727
|
+
// --- Work-stealing helpers ---
|
|
728
|
+
getOrCreateDeque(worker) {
|
|
729
|
+
let deque = this.workerDeques.get(worker);
|
|
730
|
+
if (!deque) {
|
|
731
|
+
deque = { high: [], normal: [], low: [] };
|
|
732
|
+
this.workerDeques.set(worker, deque);
|
|
733
|
+
}
|
|
734
|
+
return deque;
|
|
735
|
+
}
|
|
736
|
+
dequeSize(worker) {
|
|
737
|
+
const deque = this.workerDeques.get(worker);
|
|
738
|
+
if (!deque) return 0;
|
|
739
|
+
return deque.high.length + deque.normal.length + deque.low.length;
|
|
740
|
+
}
|
|
741
|
+
enqueueToWorker(worker, task2) {
|
|
742
|
+
this.getOrCreateDeque(worker)[task2.priority].push(task2);
|
|
743
|
+
}
|
|
744
|
+
/** Pop from own deque — FIFO within each priority level. */
|
|
745
|
+
dequeueFromOwn(worker) {
|
|
746
|
+
const deque = this.workerDeques.get(worker);
|
|
747
|
+
if (!deque) return void 0;
|
|
748
|
+
return deque.high.shift() ?? deque.normal.shift() ?? deque.low.shift();
|
|
749
|
+
}
|
|
750
|
+
/** Steal from a victim's deque — takes lowest-priority work from the back. */
|
|
751
|
+
stealFrom(victim) {
|
|
752
|
+
const deque = this.workerDeques.get(victim);
|
|
753
|
+
if (!deque) return void 0;
|
|
754
|
+
return deque.low.pop() ?? deque.normal.pop() ?? deque.high.pop();
|
|
755
|
+
}
|
|
756
|
+
/** Find the exclusive worker with the shortest deque to push a new task to. */
|
|
757
|
+
findShortestDequeWorker() {
|
|
758
|
+
let best;
|
|
759
|
+
let bestSize = Infinity;
|
|
760
|
+
const seen = /* @__PURE__ */ new Set();
|
|
761
|
+
for (const worker of this.exclusiveWorkers.values()) {
|
|
762
|
+
if (seen.has(worker)) continue;
|
|
763
|
+
seen.add(worker);
|
|
764
|
+
const size = this.dequeSize(worker);
|
|
765
|
+
if (size < bestSize) {
|
|
766
|
+
bestSize = size;
|
|
767
|
+
best = worker;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return best;
|
|
771
|
+
}
|
|
772
|
+
/** Steal a task from the busiest worker's deque, excluding the thief. */
|
|
773
|
+
stealFromBusiest(thief) {
|
|
774
|
+
let victim;
|
|
775
|
+
let maxSize = 0;
|
|
776
|
+
for (const [worker, deque] of this.workerDeques) {
|
|
777
|
+
if (worker === thief) continue;
|
|
778
|
+
const size = deque.high.length + deque.normal.length + deque.low.length;
|
|
779
|
+
if (size > maxSize) {
|
|
780
|
+
maxSize = size;
|
|
781
|
+
victim = worker;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (!victim || maxSize === 0) return void 0;
|
|
785
|
+
return this.stealFrom(victim);
|
|
786
|
+
}
|
|
787
|
+
/** Steal from any deque (no thief exclusion — used by resize). */
|
|
788
|
+
stealFromAny() {
|
|
789
|
+
let victim;
|
|
790
|
+
let maxSize = 0;
|
|
791
|
+
for (const [worker, deque] of this.workerDeques) {
|
|
792
|
+
const size = deque.high.length + deque.normal.length + deque.low.length;
|
|
793
|
+
if (size > maxSize) {
|
|
794
|
+
maxSize = size;
|
|
795
|
+
victim = worker;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (!victim || maxSize === 0) return void 0;
|
|
799
|
+
return this.stealFrom(victim);
|
|
800
|
+
}
|
|
801
|
+
/** Remove a task by ID from any worker's deque. */
|
|
802
|
+
removeFromDeques(taskId) {
|
|
803
|
+
for (const [, deque] of this.workerDeques) {
|
|
804
|
+
for (const priority of ["high", "normal", "low"]) {
|
|
805
|
+
const queue = deque[priority];
|
|
806
|
+
const idx = queue.findIndex((t) => t.id === taskId);
|
|
807
|
+
if (idx !== -1) {
|
|
808
|
+
return queue.splice(idx, 1)[0];
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return void 0;
|
|
813
|
+
}
|
|
814
|
+
/** Flush a worker's deque back to the global queue (for redistribution). */
|
|
815
|
+
flushDeque(worker) {
|
|
816
|
+
const deque = this.workerDeques.get(worker);
|
|
817
|
+
if (!deque) return;
|
|
818
|
+
for (const priority of ["high", "normal", "low"]) {
|
|
819
|
+
for (const task2 of deque[priority]) {
|
|
820
|
+
this.queues[priority].push(task2);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
this.workerDeques.delete(worker);
|
|
824
|
+
}
|
|
825
|
+
/** Clean up a deque if it's empty. */
|
|
826
|
+
cleanupDeque(worker) {
|
|
827
|
+
if (this.dequeSize(worker) === 0) {
|
|
828
|
+
this.workerDeques.delete(worker);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
658
831
|
// --- Submit ---
|
|
659
832
|
submit(task2) {
|
|
660
833
|
if (this.draining) {
|
|
@@ -680,6 +853,13 @@ var WorkerPool = class {
|
|
|
680
853
|
this.createAndReadyWorker();
|
|
681
854
|
return;
|
|
682
855
|
}
|
|
856
|
+
if (this.config.strategy === "work-stealing") {
|
|
857
|
+
const target = this.findShortestDequeWorker();
|
|
858
|
+
if (target) {
|
|
859
|
+
this.enqueueToWorker(target, task2);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
683
863
|
this.enqueue(task2);
|
|
684
864
|
}
|
|
685
865
|
submitConcurrent(task2) {
|
|
@@ -717,6 +897,7 @@ var WorkerPool = class {
|
|
|
717
897
|
type: "execute",
|
|
718
898
|
taskId: task2.id,
|
|
719
899
|
fnStr: task2.fnStr,
|
|
900
|
+
args: task2.args,
|
|
720
901
|
concurrent: false,
|
|
721
902
|
channels: task2.channels
|
|
722
903
|
};
|
|
@@ -738,6 +919,7 @@ var WorkerPool = class {
|
|
|
738
919
|
type: "execute",
|
|
739
920
|
taskId: task2.id,
|
|
740
921
|
fnStr: task2.fnStr,
|
|
922
|
+
args: task2.args,
|
|
741
923
|
concurrent: true,
|
|
742
924
|
channels: task2.channels
|
|
743
925
|
};
|
|
@@ -784,6 +966,19 @@ var WorkerPool = class {
|
|
|
784
966
|
}
|
|
785
967
|
assignNextOrIdle(worker) {
|
|
786
968
|
if (!this.allWorkers.has(worker)) return;
|
|
969
|
+
if (this.config.strategy === "work-stealing") {
|
|
970
|
+
const own = this.dequeueFromOwn(worker);
|
|
971
|
+
if (own) {
|
|
972
|
+
this.cleanupDeque(worker);
|
|
973
|
+
this.dispatch(worker, own);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
const stolen = this.stealFromBusiest(worker);
|
|
977
|
+
if (stolen) {
|
|
978
|
+
this.dispatch(worker, stolen);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
787
982
|
const next = this.dequeue();
|
|
788
983
|
if (next) {
|
|
789
984
|
this.dispatch(worker, next);
|
|
@@ -794,6 +989,7 @@ var WorkerPool = class {
|
|
|
794
989
|
this.dispatchConcurrent(worker, concurrentNext);
|
|
795
990
|
return;
|
|
796
991
|
}
|
|
992
|
+
this.cleanupDeque(worker);
|
|
797
993
|
this.makeIdle(worker);
|
|
798
994
|
}
|
|
799
995
|
assignNextConcurrentOrIdle(worker) {
|
|
@@ -813,6 +1009,13 @@ var WorkerPool = class {
|
|
|
813
1009
|
const exclusiveTask = this.dequeue();
|
|
814
1010
|
if (exclusiveTask) {
|
|
815
1011
|
this.dispatch(worker, exclusiveTask);
|
|
1012
|
+
} else if (this.config.strategy === "work-stealing") {
|
|
1013
|
+
const stolen = this.stealFromBusiest(worker);
|
|
1014
|
+
if (stolen) {
|
|
1015
|
+
this.dispatch(worker, stolen);
|
|
1016
|
+
} else {
|
|
1017
|
+
this.makeIdle(worker);
|
|
1018
|
+
}
|
|
816
1019
|
} else {
|
|
817
1020
|
this.makeIdle(worker);
|
|
818
1021
|
}
|
|
@@ -908,6 +1111,13 @@ var WorkerPool = class {
|
|
|
908
1111
|
removed.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
909
1112
|
return;
|
|
910
1113
|
}
|
|
1114
|
+
if (this.config.strategy === "work-stealing") {
|
|
1115
|
+
const removedFromDeque = this.removeFromDeques(taskId);
|
|
1116
|
+
if (removedFromDeque) {
|
|
1117
|
+
removedFromDeque.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
911
1121
|
const removedConcurrent = this.removeFromConcurrentQueue(taskId);
|
|
912
1122
|
if (removedConcurrent) {
|
|
913
1123
|
removedConcurrent.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
@@ -918,6 +1128,7 @@ var WorkerPool = class {
|
|
|
918
1128
|
this.exclusiveWorkers.delete(taskId);
|
|
919
1129
|
this.allWorkers.delete(exclusiveWorker);
|
|
920
1130
|
this.taskMap.delete(taskId);
|
|
1131
|
+
this.flushDeque(exclusiveWorker);
|
|
921
1132
|
exclusiveWorker.terminate();
|
|
922
1133
|
return;
|
|
923
1134
|
}
|
|
@@ -949,6 +1160,14 @@ var WorkerPool = class {
|
|
|
949
1160
|
}
|
|
950
1161
|
this.concurrentQueues[priority] = [];
|
|
951
1162
|
}
|
|
1163
|
+
for (const [, deque] of this.workerDeques) {
|
|
1164
|
+
for (const priority of ["high", "normal", "low"]) {
|
|
1165
|
+
for (const task2 of deque[priority]) {
|
|
1166
|
+
task2.reject(new Error("Pool is shutting down"));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
this.workerDeques.clear();
|
|
952
1171
|
for (const [taskId] of this.exclusiveWorkers) {
|
|
953
1172
|
this.taskMap.delete(taskId);
|
|
954
1173
|
}
|
|
@@ -976,7 +1195,7 @@ var WorkerPool = class {
|
|
|
976
1195
|
while (true) {
|
|
977
1196
|
const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
|
|
978
1197
|
if (totalWorkers >= maxThreads) break;
|
|
979
|
-
const task2 = this.dequeue() ?? this.dequeueConcurrent();
|
|
1198
|
+
const task2 = this.dequeue() ?? (this.config.strategy === "work-stealing" ? this.stealFromAny() : void 0) ?? this.dequeueConcurrent();
|
|
980
1199
|
if (!task2) break;
|
|
981
1200
|
this.pendingWorkerCount++;
|
|
982
1201
|
this.pendingTasksForWorkers.push(task2);
|
|
@@ -1039,6 +1258,7 @@ var WorkerPool = class {
|
|
|
1039
1258
|
this.idleWorkers.splice(idleIdx, 1);
|
|
1040
1259
|
}
|
|
1041
1260
|
this.rejectExclusiveTaskForWorker(worker, new Error("Worker exited unexpectedly"));
|
|
1261
|
+
this.flushDeque(worker);
|
|
1042
1262
|
const taskSet = this.sharedWorkers.get(worker);
|
|
1043
1263
|
if (taskSet) {
|
|
1044
1264
|
for (const taskId of taskSet) {
|
|
@@ -1054,6 +1274,17 @@ var WorkerPool = class {
|
|
|
1054
1274
|
for (const taskSet of this.sharedWorkers.values()) {
|
|
1055
1275
|
concurrentTasks += taskSet.size;
|
|
1056
1276
|
}
|
|
1277
|
+
let dequeHigh = 0;
|
|
1278
|
+
let dequeNormal = 0;
|
|
1279
|
+
let dequeLow = 0;
|
|
1280
|
+
for (const deque of this.workerDeques.values()) {
|
|
1281
|
+
dequeHigh += deque.high.length;
|
|
1282
|
+
dequeNormal += deque.normal.length;
|
|
1283
|
+
dequeLow += deque.low.length;
|
|
1284
|
+
}
|
|
1285
|
+
const queuedHigh = this.queues.high.length + dequeHigh;
|
|
1286
|
+
const queuedNormal = this.queues.normal.length + dequeNormal;
|
|
1287
|
+
const queuedLow = this.queues.low.length + dequeLow;
|
|
1057
1288
|
return {
|
|
1058
1289
|
totalWorkers: this.allWorkers.size,
|
|
1059
1290
|
idleWorkers: this.idleWorkers.length,
|
|
@@ -1062,10 +1293,10 @@ var WorkerPool = class {
|
|
|
1062
1293
|
concurrentTasks,
|
|
1063
1294
|
pendingWorkers: this.pendingWorkerCount,
|
|
1064
1295
|
queuedTasks: {
|
|
1065
|
-
high:
|
|
1066
|
-
normal:
|
|
1067
|
-
low:
|
|
1068
|
-
total:
|
|
1296
|
+
high: queuedHigh,
|
|
1297
|
+
normal: queuedNormal,
|
|
1298
|
+
low: queuedLow,
|
|
1299
|
+
total: queuedHigh + queuedNormal + queuedLow
|
|
1069
1300
|
},
|
|
1070
1301
|
queuedConcurrentTasks: {
|
|
1071
1302
|
high: this.concurrentQueues.high.length,
|
|
@@ -1120,7 +1351,7 @@ var taskCounter = 0;
|
|
|
1120
1351
|
function spawn(fn, opts) {
|
|
1121
1352
|
const fnStr = serializeFunction(fn);
|
|
1122
1353
|
const taskId = String(++taskCounter);
|
|
1123
|
-
const
|
|
1354
|
+
const spawnError = new Error();
|
|
1124
1355
|
let resolveFn;
|
|
1125
1356
|
let rejectFn;
|
|
1126
1357
|
let settled = false;
|
|
@@ -1150,14 +1381,29 @@ function spawn(fn, opts) {
|
|
|
1150
1381
|
reject: (reason) => {
|
|
1151
1382
|
if (!settled) {
|
|
1152
1383
|
settled = true;
|
|
1153
|
-
if (reason instanceof Error
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1384
|
+
if (reason instanceof Error) {
|
|
1385
|
+
const spawnStack = spawnError.stack;
|
|
1386
|
+
if (spawnStack) {
|
|
1387
|
+
const callerLine = spawnStack.split("\n").slice(2).join("\n");
|
|
1388
|
+
reason.stack = (reason.stack ?? reason.message) + "\n --- spawned at ---\n" + callerLine;
|
|
1389
|
+
}
|
|
1156
1390
|
}
|
|
1157
1391
|
rejectFn(reason);
|
|
1158
1392
|
}
|
|
1159
1393
|
}
|
|
1160
1394
|
};
|
|
1395
|
+
const ctx = opts?.ctx;
|
|
1396
|
+
if (ctx) {
|
|
1397
|
+
if (ctx.signal.aborted) {
|
|
1398
|
+
settled = true;
|
|
1399
|
+
rejectFn(ctx.err ?? new DOMException("Task was cancelled", "AbortError"));
|
|
1400
|
+
return {
|
|
1401
|
+
result,
|
|
1402
|
+
cancel: () => {
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1161
1407
|
getPool().submit(task2);
|
|
1162
1408
|
const cancel = () => {
|
|
1163
1409
|
if (settled) return;
|
|
@@ -1165,6 +1411,14 @@ function spawn(fn, opts) {
|
|
|
1165
1411
|
getPool().cancelTask(taskId);
|
|
1166
1412
|
rejectFn(new DOMException("Task was cancelled", "AbortError"));
|
|
1167
1413
|
};
|
|
1414
|
+
if (ctx) {
|
|
1415
|
+
const onAbort = () => cancel();
|
|
1416
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
1417
|
+
result.then(
|
|
1418
|
+
() => ctx.signal.removeEventListener("abort", onAbort),
|
|
1419
|
+
() => ctx.signal.removeEventListener("abort", onAbort)
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1168
1422
|
return { result, cancel };
|
|
1169
1423
|
}
|
|
1170
1424
|
|
|
@@ -1172,6 +1426,17 @@ function spawn(fn, opts) {
|
|
|
1172
1426
|
var WaitGroup = class {
|
|
1173
1427
|
tasks = [];
|
|
1174
1428
|
controller = new AbortController();
|
|
1429
|
+
ctx;
|
|
1430
|
+
constructor(ctx) {
|
|
1431
|
+
this.ctx = ctx;
|
|
1432
|
+
if (ctx) {
|
|
1433
|
+
if (ctx.signal.aborted) {
|
|
1434
|
+
this.controller.abort();
|
|
1435
|
+
} else {
|
|
1436
|
+
ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1175
1440
|
/**
|
|
1176
1441
|
* An `AbortSignal` shared across all tasks in this group.
|
|
1177
1442
|
* Pass it into spawned functions so they can stop early when `cancel()` is called.
|
|
@@ -1188,7 +1453,7 @@ var WaitGroup = class {
|
|
|
1188
1453
|
if (this.controller.signal.aborted) {
|
|
1189
1454
|
throw new Error("WaitGroup has been cancelled");
|
|
1190
1455
|
}
|
|
1191
|
-
const handle = spawn(fn, opts);
|
|
1456
|
+
const handle = spawn(fn, { ...opts, ctx: this.ctx });
|
|
1192
1457
|
this.tasks.push(handle);
|
|
1193
1458
|
}
|
|
1194
1459
|
/**
|
|
@@ -1223,22 +1488,81 @@ var ErrGroup = class {
|
|
|
1223
1488
|
controller = new AbortController();
|
|
1224
1489
|
firstError = null;
|
|
1225
1490
|
hasError = false;
|
|
1491
|
+
ctx;
|
|
1492
|
+
limit = 0;
|
|
1493
|
+
// 0 = unlimited
|
|
1494
|
+
inFlight = 0;
|
|
1495
|
+
waiting = [];
|
|
1496
|
+
constructor(ctx) {
|
|
1497
|
+
this.ctx = ctx;
|
|
1498
|
+
if (ctx) {
|
|
1499
|
+
if (ctx.signal.aborted) {
|
|
1500
|
+
this.controller.abort();
|
|
1501
|
+
} else {
|
|
1502
|
+
ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1226
1506
|
get signal() {
|
|
1227
1507
|
return this.controller.signal;
|
|
1228
1508
|
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Set the maximum number of tasks that can run concurrently.
|
|
1511
|
+
* Like Go's `errgroup.SetLimit()`. Must be called before any `spawn()`.
|
|
1512
|
+
* A value of 0 (default) means unlimited.
|
|
1513
|
+
*/
|
|
1514
|
+
setLimit(n) {
|
|
1515
|
+
if (this.tasks.length > 0) {
|
|
1516
|
+
throw new Error("SetLimit must be called before any spawn()");
|
|
1517
|
+
}
|
|
1518
|
+
if (n < 0 || !Number.isInteger(n)) {
|
|
1519
|
+
throw new RangeError("Limit must be a non-negative integer");
|
|
1520
|
+
}
|
|
1521
|
+
this.limit = n;
|
|
1522
|
+
}
|
|
1229
1523
|
spawn(fn, opts) {
|
|
1230
1524
|
if (this.controller.signal.aborted) {
|
|
1231
1525
|
throw new Error("ErrGroup has been cancelled");
|
|
1232
1526
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1527
|
+
if (this.limit > 0 && this.inFlight >= this.limit) {
|
|
1528
|
+
let innerCancel = () => {
|
|
1529
|
+
};
|
|
1530
|
+
let cancelled = false;
|
|
1531
|
+
const cancel = () => {
|
|
1532
|
+
cancelled = true;
|
|
1533
|
+
innerCancel();
|
|
1534
|
+
};
|
|
1535
|
+
const result = new Promise((resolve) => {
|
|
1536
|
+
this.waiting.push(resolve);
|
|
1537
|
+
}).then(() => {
|
|
1538
|
+
if (cancelled) throw new DOMException("Task was cancelled", "AbortError");
|
|
1539
|
+
const handle2 = this.doSpawn(fn, opts);
|
|
1540
|
+
innerCancel = handle2.cancel;
|
|
1541
|
+
return handle2.result;
|
|
1542
|
+
});
|
|
1543
|
+
this.tasks.push({ result, cancel });
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
const handle = this.doSpawn(fn, opts);
|
|
1547
|
+
this.tasks.push(handle);
|
|
1548
|
+
}
|
|
1549
|
+
doSpawn(fn, opts) {
|
|
1550
|
+
this.inFlight++;
|
|
1551
|
+
const handle = spawn(fn, { ...opts, ctx: this.ctx });
|
|
1552
|
+
const onSettle = () => {
|
|
1553
|
+
this.inFlight--;
|
|
1554
|
+
const next = this.waiting.shift();
|
|
1555
|
+
if (next) next();
|
|
1556
|
+
};
|
|
1557
|
+
handle.result.then(onSettle, (err) => {
|
|
1558
|
+
onSettle();
|
|
1235
1559
|
if (!this.hasError) {
|
|
1236
1560
|
this.hasError = true;
|
|
1237
1561
|
this.firstError = err;
|
|
1238
1562
|
this.cancel();
|
|
1239
1563
|
}
|
|
1240
1564
|
});
|
|
1241
|
-
|
|
1565
|
+
return handle;
|
|
1242
1566
|
}
|
|
1243
1567
|
async wait() {
|
|
1244
1568
|
const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
|
|
@@ -1294,6 +1618,212 @@ var Mutex = class {
|
|
|
1294
1618
|
return this.locked;
|
|
1295
1619
|
}
|
|
1296
1620
|
};
|
|
1621
|
+
var RWMutex = class {
|
|
1622
|
+
readers = 0;
|
|
1623
|
+
writing = false;
|
|
1624
|
+
readQueue = [];
|
|
1625
|
+
writeQueue = [];
|
|
1626
|
+
async rLock() {
|
|
1627
|
+
if (!this.writing && this.writeQueue.length === 0) {
|
|
1628
|
+
this.readers++;
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
return new Promise((resolve) => {
|
|
1632
|
+
this.readQueue.push(() => {
|
|
1633
|
+
this.readers++;
|
|
1634
|
+
resolve();
|
|
1635
|
+
});
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
rUnlock() {
|
|
1639
|
+
if (this.readers <= 0) {
|
|
1640
|
+
throw new Error("Cannot rUnlock a RWMutex that is not read-locked");
|
|
1641
|
+
}
|
|
1642
|
+
this.readers--;
|
|
1643
|
+
if (this.readers === 0) {
|
|
1644
|
+
this.wakeWriter();
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async lock() {
|
|
1648
|
+
if (!this.writing && this.readers === 0) {
|
|
1649
|
+
this.writing = true;
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
return new Promise((resolve) => {
|
|
1653
|
+
this.writeQueue.push(() => {
|
|
1654
|
+
this.writing = true;
|
|
1655
|
+
resolve();
|
|
1656
|
+
});
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
unlock() {
|
|
1660
|
+
if (!this.writing) {
|
|
1661
|
+
throw new Error("Cannot unlock a RWMutex that is not write-locked");
|
|
1662
|
+
}
|
|
1663
|
+
this.writing = false;
|
|
1664
|
+
if (this.readQueue.length > 0) {
|
|
1665
|
+
this.wakeReaders();
|
|
1666
|
+
} else {
|
|
1667
|
+
this.wakeWriter();
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
async withRLock(fn) {
|
|
1671
|
+
await this.rLock();
|
|
1672
|
+
try {
|
|
1673
|
+
return await fn();
|
|
1674
|
+
} finally {
|
|
1675
|
+
this.rUnlock();
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async withLock(fn) {
|
|
1679
|
+
await this.lock();
|
|
1680
|
+
try {
|
|
1681
|
+
return await fn();
|
|
1682
|
+
} finally {
|
|
1683
|
+
this.unlock();
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
get isLocked() {
|
|
1687
|
+
return this.writing || this.readers > 0;
|
|
1688
|
+
}
|
|
1689
|
+
wakeReaders() {
|
|
1690
|
+
const queue = this.readQueue;
|
|
1691
|
+
this.readQueue = [];
|
|
1692
|
+
for (const wake of queue) {
|
|
1693
|
+
wake();
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
wakeWriter() {
|
|
1697
|
+
const next = this.writeQueue.shift();
|
|
1698
|
+
if (next) next();
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
// src/semaphore.ts
|
|
1703
|
+
var Semaphore = class {
|
|
1704
|
+
max;
|
|
1705
|
+
cur;
|
|
1706
|
+
queue = [];
|
|
1707
|
+
constructor(n) {
|
|
1708
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1709
|
+
throw new Error("Semaphore capacity must be a positive integer");
|
|
1710
|
+
}
|
|
1711
|
+
this.max = n;
|
|
1712
|
+
this.cur = 0;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Acquire `n` permits, waiting if necessary until they are available.
|
|
1716
|
+
* Acquires are served in FIFO order.
|
|
1717
|
+
*/
|
|
1718
|
+
async acquire(n = 1) {
|
|
1719
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1720
|
+
throw new Error("Acquire count must be a positive integer");
|
|
1721
|
+
}
|
|
1722
|
+
if (n > this.max) {
|
|
1723
|
+
throw new Error(`Acquire count ${n} exceeds semaphore capacity ${this.max}`);
|
|
1724
|
+
}
|
|
1725
|
+
if (this.cur + n <= this.max && this.queue.length === 0) {
|
|
1726
|
+
this.cur += n;
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
return new Promise((resolve) => {
|
|
1730
|
+
this.queue.push({ n, resolve });
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Try to acquire `n` permits without blocking.
|
|
1735
|
+
* Returns `true` if successful, `false` if not enough permits are available.
|
|
1736
|
+
*/
|
|
1737
|
+
tryAcquire(n = 1) {
|
|
1738
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1739
|
+
throw new Error("Acquire count must be a positive integer");
|
|
1740
|
+
}
|
|
1741
|
+
if (n > this.max) {
|
|
1742
|
+
throw new Error(`Acquire count ${n} exceeds semaphore capacity ${this.max}`);
|
|
1743
|
+
}
|
|
1744
|
+
if (this.cur + n <= this.max && this.queue.length === 0) {
|
|
1745
|
+
this.cur += n;
|
|
1746
|
+
return true;
|
|
1747
|
+
}
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Release `n` permits, potentially waking queued acquirers.
|
|
1752
|
+
*/
|
|
1753
|
+
release(n = 1) {
|
|
1754
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1755
|
+
throw new Error("Release count must be a positive integer");
|
|
1756
|
+
}
|
|
1757
|
+
if (this.cur - n < 0) {
|
|
1758
|
+
throw new Error("Released more permits than acquired");
|
|
1759
|
+
}
|
|
1760
|
+
this.cur -= n;
|
|
1761
|
+
this.wake();
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Acquire `n` permits, run `fn`, then release automatically — even if `fn` throws.
|
|
1765
|
+
*/
|
|
1766
|
+
async withAcquire(fn, n = 1) {
|
|
1767
|
+
await this.acquire(n);
|
|
1768
|
+
try {
|
|
1769
|
+
return await fn();
|
|
1770
|
+
} finally {
|
|
1771
|
+
this.release(n);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
/** Number of permits currently available. */
|
|
1775
|
+
get available() {
|
|
1776
|
+
return this.max - this.cur;
|
|
1777
|
+
}
|
|
1778
|
+
/** Total capacity of the semaphore. */
|
|
1779
|
+
get capacity() {
|
|
1780
|
+
return this.max;
|
|
1781
|
+
}
|
|
1782
|
+
wake() {
|
|
1783
|
+
while (this.queue.length > 0) {
|
|
1784
|
+
const head = this.queue[0];
|
|
1785
|
+
if (this.cur + head.n > this.max) break;
|
|
1786
|
+
this.queue.shift();
|
|
1787
|
+
this.cur += head.n;
|
|
1788
|
+
head.resolve();
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
// src/cond.ts
|
|
1794
|
+
var Cond = class {
|
|
1795
|
+
mu;
|
|
1796
|
+
waiters = [];
|
|
1797
|
+
constructor(mu) {
|
|
1798
|
+
this.mu = mu;
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Atomically releases the mutex, suspends the caller until `signal()` or `broadcast()`
|
|
1802
|
+
* is called, then re-acquires the mutex before returning.
|
|
1803
|
+
*
|
|
1804
|
+
* Must be called while holding the mutex.
|
|
1805
|
+
*/
|
|
1806
|
+
async wait() {
|
|
1807
|
+
this.mu.unlock();
|
|
1808
|
+
await new Promise((resolve) => {
|
|
1809
|
+
this.waiters.push(resolve);
|
|
1810
|
+
});
|
|
1811
|
+
await this.mu.lock();
|
|
1812
|
+
}
|
|
1813
|
+
/** Wake one waiting task (if any). */
|
|
1814
|
+
signal() {
|
|
1815
|
+
const next = this.waiters.shift();
|
|
1816
|
+
if (next) next();
|
|
1817
|
+
}
|
|
1818
|
+
/** Wake all waiting tasks. */
|
|
1819
|
+
broadcast() {
|
|
1820
|
+
const queue = this.waiters;
|
|
1821
|
+
this.waiters = [];
|
|
1822
|
+
for (const wake of queue) {
|
|
1823
|
+
wake();
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1297
1827
|
|
|
1298
1828
|
// src/once.ts
|
|
1299
1829
|
var Once = class {
|
|
@@ -1435,23 +1965,69 @@ function ticker(ms) {
|
|
|
1435
1965
|
return new Ticker(ms);
|
|
1436
1966
|
}
|
|
1437
1967
|
|
|
1968
|
+
// src/timer.ts
|
|
1969
|
+
var Timer = class {
|
|
1970
|
+
timer = null;
|
|
1971
|
+
_stopped = false;
|
|
1972
|
+
/** Promise that resolves when the timer fires. Replaced on `reset()`. */
|
|
1973
|
+
channel;
|
|
1974
|
+
constructor(ms) {
|
|
1975
|
+
this.channel = this.schedule(ms);
|
|
1976
|
+
}
|
|
1977
|
+
schedule(ms) {
|
|
1978
|
+
return new Promise((resolve) => {
|
|
1979
|
+
this.timer = setTimeout(() => {
|
|
1980
|
+
this._stopped = true;
|
|
1981
|
+
this.timer = null;
|
|
1982
|
+
resolve();
|
|
1983
|
+
}, ms);
|
|
1984
|
+
if (typeof this.timer === "object" && "unref" in this.timer) {
|
|
1985
|
+
this.timer.unref();
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Stop the timer. Returns `true` if the timer was pending (stopped before firing),
|
|
1991
|
+
* `false` if it had already fired or was already stopped.
|
|
1992
|
+
*
|
|
1993
|
+
* After stopping, the current `channel` promise will never resolve.
|
|
1994
|
+
*/
|
|
1995
|
+
stop() {
|
|
1996
|
+
if (this.timer === null) return false;
|
|
1997
|
+
clearTimeout(this.timer);
|
|
1998
|
+
this.timer = null;
|
|
1999
|
+
this._stopped = true;
|
|
2000
|
+
return true;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Reset the timer to fire after `ms` milliseconds.
|
|
2004
|
+
* If the timer was pending, it is stopped first. Creates a new `channel` promise.
|
|
2005
|
+
*/
|
|
2006
|
+
reset(ms) {
|
|
2007
|
+
this.stop();
|
|
2008
|
+
this._stopped = false;
|
|
2009
|
+
this.channel = this.schedule(ms);
|
|
2010
|
+
}
|
|
2011
|
+
/** Whether the timer has fired or been stopped. */
|
|
2012
|
+
get stopped() {
|
|
2013
|
+
return this._stopped;
|
|
2014
|
+
}
|
|
2015
|
+
};
|
|
2016
|
+
|
|
1438
2017
|
// src/registry.ts
|
|
1439
2018
|
var taskCounter2 = 0;
|
|
1440
2019
|
function task(fn) {
|
|
2020
|
+
const fnStr = serializeFunction(fn);
|
|
1441
2021
|
return (...args) => {
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
const json = JSON.stringify(a);
|
|
1445
|
-
if (json === void 0) {
|
|
2022
|
+
for (const a of args) {
|
|
2023
|
+
if (JSON.stringify(a) === void 0) {
|
|
1446
2024
|
throw new TypeError(
|
|
1447
2025
|
`Argument of type ${typeof a} is not JSON-serializable. task() args must be JSON-serializable (no undefined, functions, symbols, or BigInt).`
|
|
1448
2026
|
);
|
|
1449
2027
|
}
|
|
1450
|
-
|
|
1451
|
-
});
|
|
1452
|
-
const wrapperStr = `() => (${fnStr})(${serializedArgs.join(", ")})`;
|
|
2028
|
+
}
|
|
1453
2029
|
const taskId = `task_${++taskCounter2}`;
|
|
1454
|
-
const
|
|
2030
|
+
const spawnError = new Error();
|
|
1455
2031
|
let resolveFn;
|
|
1456
2032
|
let rejectFn;
|
|
1457
2033
|
const result = new Promise((resolve, reject) => {
|
|
@@ -1460,14 +2036,18 @@ function task(fn) {
|
|
|
1460
2036
|
});
|
|
1461
2037
|
const taskObj = {
|
|
1462
2038
|
id: taskId,
|
|
1463
|
-
fnStr
|
|
2039
|
+
fnStr,
|
|
2040
|
+
args,
|
|
1464
2041
|
priority: "normal",
|
|
1465
2042
|
concurrent: false,
|
|
1466
2043
|
resolve: (value) => resolveFn(value),
|
|
1467
2044
|
reject: (reason) => {
|
|
1468
|
-
if (reason instanceof Error
|
|
1469
|
-
const
|
|
1470
|
-
|
|
2045
|
+
if (reason instanceof Error) {
|
|
2046
|
+
const spawnStack = spawnError.stack;
|
|
2047
|
+
if (spawnStack) {
|
|
2048
|
+
const callerLine = spawnStack.split("\n").slice(2).join("\n");
|
|
2049
|
+
reason.stack = (reason.stack ?? reason.message) + "\n --- spawned at ---\n" + callerLine;
|
|
2050
|
+
}
|
|
1471
2051
|
}
|
|
1472
2052
|
rejectFn(reason);
|
|
1473
2053
|
}
|
|
@@ -1476,13 +2056,179 @@ function task(fn) {
|
|
|
1476
2056
|
return result;
|
|
1477
2057
|
};
|
|
1478
2058
|
}
|
|
2059
|
+
|
|
2060
|
+
// src/context.ts
|
|
2061
|
+
var CancelledError = class extends Error {
|
|
2062
|
+
constructor(message = "context cancelled") {
|
|
2063
|
+
super(message);
|
|
2064
|
+
this.name = "CancelledError";
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
var DeadlineExceededError = class extends Error {
|
|
2068
|
+
constructor() {
|
|
2069
|
+
super("context deadline exceeded");
|
|
2070
|
+
this.name = "DeadlineExceededError";
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
var BaseContext = class {
|
|
2074
|
+
_err = null;
|
|
2075
|
+
controller;
|
|
2076
|
+
parent;
|
|
2077
|
+
constructor(parent) {
|
|
2078
|
+
this.parent = parent;
|
|
2079
|
+
this.controller = new AbortController();
|
|
2080
|
+
if (parent) {
|
|
2081
|
+
if (parent.signal.aborted) {
|
|
2082
|
+
this._err = parent.err ?? new CancelledError();
|
|
2083
|
+
this.controller.abort();
|
|
2084
|
+
} else {
|
|
2085
|
+
parent.signal.addEventListener(
|
|
2086
|
+
"abort",
|
|
2087
|
+
() => {
|
|
2088
|
+
if (!this.controller.signal.aborted) {
|
|
2089
|
+
this._err = parent.err ?? new CancelledError();
|
|
2090
|
+
this.controller.abort();
|
|
2091
|
+
}
|
|
2092
|
+
},
|
|
2093
|
+
{ once: true }
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
get signal() {
|
|
2099
|
+
return this.controller.signal;
|
|
2100
|
+
}
|
|
2101
|
+
get deadline() {
|
|
2102
|
+
return this.parent?.deadline ?? null;
|
|
2103
|
+
}
|
|
2104
|
+
get err() {
|
|
2105
|
+
return this._err;
|
|
2106
|
+
}
|
|
2107
|
+
value(_key) {
|
|
2108
|
+
return this.parent?.value(_key);
|
|
2109
|
+
}
|
|
2110
|
+
done() {
|
|
2111
|
+
if (this.controller.signal.aborted) return Promise.resolve();
|
|
2112
|
+
return new Promise((resolve) => {
|
|
2113
|
+
this.controller.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
};
|
|
2117
|
+
var BackgroundContext = class {
|
|
2118
|
+
_signal = new AbortController().signal;
|
|
2119
|
+
get signal() {
|
|
2120
|
+
return this._signal;
|
|
2121
|
+
}
|
|
2122
|
+
get deadline() {
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
get err() {
|
|
2126
|
+
return null;
|
|
2127
|
+
}
|
|
2128
|
+
value(_key) {
|
|
2129
|
+
return void 0;
|
|
2130
|
+
}
|
|
2131
|
+
done() {
|
|
2132
|
+
return new Promise(() => {
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
var bg = null;
|
|
2137
|
+
function background() {
|
|
2138
|
+
if (!bg) bg = new BackgroundContext();
|
|
2139
|
+
return bg;
|
|
2140
|
+
}
|
|
2141
|
+
var CancelContext = class extends BaseContext {
|
|
2142
|
+
cancel(reason) {
|
|
2143
|
+
if (!this.controller.signal.aborted) {
|
|
2144
|
+
this._err = new CancelledError(reason ?? "context cancelled");
|
|
2145
|
+
this.controller.abort();
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
function withCancel(parent) {
|
|
2150
|
+
const ctx = new CancelContext(parent);
|
|
2151
|
+
return [ctx, (reason) => ctx.cancel(reason)];
|
|
2152
|
+
}
|
|
2153
|
+
var DeadlineContext = class extends BaseContext {
|
|
2154
|
+
_deadline;
|
|
2155
|
+
timer = null;
|
|
2156
|
+
constructor(parent, deadline) {
|
|
2157
|
+
super(parent);
|
|
2158
|
+
this._deadline = deadline;
|
|
2159
|
+
if (parent.deadline && parent.deadline < deadline) {
|
|
2160
|
+
this._deadline = parent.deadline;
|
|
2161
|
+
}
|
|
2162
|
+
if (this.controller.signal.aborted) {
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
const ms = this._deadline.getTime() - Date.now();
|
|
2166
|
+
if (ms <= 0) {
|
|
2167
|
+
this._err = new DeadlineExceededError();
|
|
2168
|
+
this.controller.abort();
|
|
2169
|
+
} else {
|
|
2170
|
+
this.timer = setTimeout(() => {
|
|
2171
|
+
if (!this.controller.signal.aborted) {
|
|
2172
|
+
this._err = new DeadlineExceededError();
|
|
2173
|
+
this.controller.abort();
|
|
2174
|
+
}
|
|
2175
|
+
}, ms);
|
|
2176
|
+
if (typeof this.timer === "object" && "unref" in this.timer) {
|
|
2177
|
+
this.timer.unref();
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
get deadline() {
|
|
2182
|
+
return this._deadline;
|
|
2183
|
+
}
|
|
2184
|
+
cancel(reason) {
|
|
2185
|
+
if (this.timer !== null) {
|
|
2186
|
+
clearTimeout(this.timer);
|
|
2187
|
+
this.timer = null;
|
|
2188
|
+
}
|
|
2189
|
+
if (!this.controller.signal.aborted) {
|
|
2190
|
+
this._err = new CancelledError(reason ?? "context cancelled");
|
|
2191
|
+
this.controller.abort();
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
function withDeadline(parent, deadline) {
|
|
2196
|
+
const ctx = new DeadlineContext(parent, deadline);
|
|
2197
|
+
return [ctx, (reason) => ctx.cancel(reason)];
|
|
2198
|
+
}
|
|
2199
|
+
function withTimeout(parent, ms) {
|
|
2200
|
+
return withDeadline(parent, new Date(Date.now() + ms));
|
|
2201
|
+
}
|
|
2202
|
+
var ValueContext = class extends BaseContext {
|
|
2203
|
+
key;
|
|
2204
|
+
val;
|
|
2205
|
+
constructor(parent, key, val) {
|
|
2206
|
+
super(parent);
|
|
2207
|
+
this.key = key;
|
|
2208
|
+
this.val = val;
|
|
2209
|
+
}
|
|
2210
|
+
value(key) {
|
|
2211
|
+
if (key === this.key) return this.val;
|
|
2212
|
+
return this.parent?.value(key);
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
function withValue(parent, key, value) {
|
|
2216
|
+
return new ValueContext(parent, key, value);
|
|
2217
|
+
}
|
|
1479
2218
|
export {
|
|
2219
|
+
CancelledError,
|
|
2220
|
+
Cond,
|
|
2221
|
+
DeadlineExceededError,
|
|
1480
2222
|
ErrGroup,
|
|
1481
2223
|
Mutex,
|
|
1482
2224
|
Once,
|
|
2225
|
+
RWMutex,
|
|
2226
|
+
Semaphore,
|
|
1483
2227
|
Ticker,
|
|
2228
|
+
Timer,
|
|
1484
2229
|
WaitGroup,
|
|
1485
2230
|
after,
|
|
2231
|
+
background,
|
|
1486
2232
|
chan,
|
|
1487
2233
|
configure,
|
|
1488
2234
|
detectCapability,
|
|
@@ -1493,6 +2239,10 @@ export {
|
|
|
1493
2239
|
spawn,
|
|
1494
2240
|
stats,
|
|
1495
2241
|
task,
|
|
1496
|
-
ticker
|
|
2242
|
+
ticker,
|
|
2243
|
+
withCancel,
|
|
2244
|
+
withDeadline,
|
|
2245
|
+
withTimeout,
|
|
2246
|
+
withValue
|
|
1497
2247
|
};
|
|
1498
2248
|
//# sourceMappingURL=index.js.map
|