@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.cjs
CHANGED
|
@@ -20,12 +20,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
CancelledError: () => CancelledError,
|
|
24
|
+
Cond: () => Cond,
|
|
25
|
+
DeadlineExceededError: () => DeadlineExceededError,
|
|
23
26
|
ErrGroup: () => ErrGroup,
|
|
24
27
|
Mutex: () => Mutex,
|
|
25
28
|
Once: () => Once,
|
|
29
|
+
RWMutex: () => RWMutex,
|
|
30
|
+
Semaphore: () => Semaphore,
|
|
26
31
|
Ticker: () => Ticker,
|
|
32
|
+
Timer: () => Timer,
|
|
27
33
|
WaitGroup: () => WaitGroup,
|
|
28
34
|
after: () => after,
|
|
35
|
+
background: () => background,
|
|
29
36
|
chan: () => chan,
|
|
30
37
|
configure: () => configure,
|
|
31
38
|
detectCapability: () => detectCapability,
|
|
@@ -36,7 +43,11 @@ __export(index_exports, {
|
|
|
36
43
|
spawn: () => spawn,
|
|
37
44
|
stats: () => stats,
|
|
38
45
|
task: () => task,
|
|
39
|
-
ticker: () => ticker
|
|
46
|
+
ticker: () => ticker,
|
|
47
|
+
withCancel: () => withCancel,
|
|
48
|
+
withDeadline: () => withDeadline,
|
|
49
|
+
withTimeout: () => withTimeout,
|
|
50
|
+
withValue: () => withValue
|
|
40
51
|
});
|
|
41
52
|
module.exports = __toCommonJS(index_exports);
|
|
42
53
|
|
|
@@ -44,10 +55,13 @@ module.exports = __toCommonJS(index_exports);
|
|
|
44
55
|
var NATIVE_CODE_RE = /\[native code\]/;
|
|
45
56
|
var METHOD_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/;
|
|
46
57
|
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_$])/;
|
|
58
|
+
var serializeCache = /* @__PURE__ */ new WeakMap();
|
|
47
59
|
function serializeFunction(fn) {
|
|
48
60
|
if (typeof fn !== "function") {
|
|
49
61
|
throw new TypeError("Expected a function");
|
|
50
62
|
}
|
|
63
|
+
const cached = serializeCache.get(fn);
|
|
64
|
+
if (cached) return cached;
|
|
51
65
|
const str = fn.toString();
|
|
52
66
|
if (typeof str !== "string" || str.length === 0) {
|
|
53
67
|
throw new TypeError(
|
|
@@ -69,6 +83,7 @@ function serializeFunction(fn) {
|
|
|
69
83
|
"Class methods cannot be serialized. Use an arrow function wrapper instead."
|
|
70
84
|
);
|
|
71
85
|
}
|
|
86
|
+
serializeCache.set(fn, str);
|
|
72
87
|
return str;
|
|
73
88
|
}
|
|
74
89
|
|
|
@@ -189,15 +204,23 @@ function __buildChannelProxies(channels) {
|
|
|
189
204
|
return proxies;
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
|
|
207
|
+
const __fnCache = new Map();
|
|
208
|
+
const __FN_CACHE_MAX = 1000;
|
|
209
|
+
|
|
210
|
+
function __execFn(fnStr, channels, args) {
|
|
211
|
+
let parsedFn = __fnCache.get(fnStr);
|
|
212
|
+
if (!parsedFn) {
|
|
213
|
+
parsedFn = (new Function('return (' + fnStr + ')'))();
|
|
214
|
+
if (__fnCache.size >= __FN_CACHE_MAX) __fnCache.delete(__fnCache.keys().next().value);
|
|
215
|
+
__fnCache.set(fnStr, parsedFn);
|
|
216
|
+
}
|
|
217
|
+
if (args) {
|
|
218
|
+
return parsedFn(...args);
|
|
219
|
+
}
|
|
193
220
|
if (channels) {
|
|
194
|
-
|
|
195
|
-
const fn = new Function('__ch', 'return (' + fnStr + ')(__ch)');
|
|
196
|
-
return fn(__ch);
|
|
197
|
-
} else {
|
|
198
|
-
const fn = new Function('return (' + fnStr + ')()');
|
|
199
|
-
return fn();
|
|
221
|
+
return parsedFn(__buildChannelProxies(channels));
|
|
200
222
|
}
|
|
223
|
+
return parsedFn();
|
|
201
224
|
}
|
|
202
225
|
`;
|
|
203
226
|
var NODE_BOOTSTRAP_CODE = `
|
|
@@ -223,7 +246,7 @@ parentPort.on('message', async (msg) => {
|
|
|
223
246
|
return;
|
|
224
247
|
}
|
|
225
248
|
try {
|
|
226
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
249
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
227
250
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
228
251
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
229
252
|
}
|
|
@@ -242,7 +265,7 @@ parentPort.on('message', async (msg) => {
|
|
|
242
265
|
})();
|
|
243
266
|
} else {
|
|
244
267
|
try {
|
|
245
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
268
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
246
269
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
247
270
|
} catch (error) {
|
|
248
271
|
parentPort.postMessage({
|
|
@@ -283,7 +306,7 @@ self.onmessage = async (event) => {
|
|
|
283
306
|
return;
|
|
284
307
|
}
|
|
285
308
|
try {
|
|
286
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
309
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
287
310
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
288
311
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
289
312
|
}
|
|
@@ -302,7 +325,7 @@ self.onmessage = async (event) => {
|
|
|
302
325
|
})();
|
|
303
326
|
} else {
|
|
304
327
|
try {
|
|
305
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
328
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
306
329
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
307
330
|
} catch (error) {
|
|
308
331
|
self.postMessage({
|
|
@@ -389,17 +412,14 @@ var BunManagedWorker = class {
|
|
|
389
412
|
on(event, handler) {
|
|
390
413
|
if (event === "message") {
|
|
391
414
|
this.worker.addEventListener("message", (e) => {
|
|
392
|
-
;
|
|
393
415
|
handler(e.data);
|
|
394
416
|
});
|
|
395
417
|
} else if (event === "error") {
|
|
396
418
|
this.worker.addEventListener("error", (e) => {
|
|
397
|
-
;
|
|
398
419
|
handler(e.error ?? new Error(e.message));
|
|
399
420
|
});
|
|
400
421
|
} else if (event === "exit") {
|
|
401
422
|
this.worker.addEventListener("close", (e) => {
|
|
402
|
-
;
|
|
403
423
|
handler(e.code ?? 0);
|
|
404
424
|
});
|
|
405
425
|
}
|
|
@@ -429,6 +449,8 @@ var ChannelImpl = class {
|
|
|
429
449
|
// constraint: can't create channels of nullable type
|
|
430
450
|
/** @internal */
|
|
431
451
|
_id;
|
|
452
|
+
/** @internal — true once the channel ID has been sent to a worker */
|
|
453
|
+
_shared = false;
|
|
432
454
|
buffer = [];
|
|
433
455
|
capacity;
|
|
434
456
|
closed = false;
|
|
@@ -439,6 +461,12 @@ var ChannelImpl = class {
|
|
|
439
461
|
this.capacity = capacity;
|
|
440
462
|
channelRegistry.set(this._id, this);
|
|
441
463
|
}
|
|
464
|
+
get len() {
|
|
465
|
+
return this.buffer.length;
|
|
466
|
+
}
|
|
467
|
+
get cap() {
|
|
468
|
+
return this.capacity;
|
|
469
|
+
}
|
|
442
470
|
send(value) {
|
|
443
471
|
if (this.closed) {
|
|
444
472
|
return Promise.reject(new Error("send on closed channel"));
|
|
@@ -464,6 +492,7 @@ var ChannelImpl = class {
|
|
|
464
492
|
this.buffer.push(sender2.value);
|
|
465
493
|
sender2.resolve();
|
|
466
494
|
}
|
|
495
|
+
this.maybeUnregister();
|
|
467
496
|
return Promise.resolve(value);
|
|
468
497
|
}
|
|
469
498
|
const sender = this.sendQueue.shift();
|
|
@@ -472,6 +501,7 @@ var ChannelImpl = class {
|
|
|
472
501
|
return Promise.resolve(sender.value);
|
|
473
502
|
}
|
|
474
503
|
if (this.closed) {
|
|
504
|
+
this.maybeUnregister();
|
|
475
505
|
return Promise.resolve(null);
|
|
476
506
|
}
|
|
477
507
|
return new Promise((resolve) => {
|
|
@@ -492,6 +522,45 @@ var ChannelImpl = class {
|
|
|
492
522
|
}
|
|
493
523
|
this.sendQueue = [];
|
|
494
524
|
}
|
|
525
|
+
maybeUnregister() {
|
|
526
|
+
if (!this._shared && this.closed && this.buffer.length === 0 && this.recvQueue.length === 0) {
|
|
527
|
+
channelRegistry.delete(this._id);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
sendOnly() {
|
|
531
|
+
const send = (value) => this.send(value);
|
|
532
|
+
const close = () => this.close();
|
|
533
|
+
const getLen = () => this.len;
|
|
534
|
+
const getCap = () => this.cap;
|
|
535
|
+
return {
|
|
536
|
+
send,
|
|
537
|
+
close,
|
|
538
|
+
get len() {
|
|
539
|
+
return getLen();
|
|
540
|
+
},
|
|
541
|
+
get cap() {
|
|
542
|
+
return getCap();
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
recvOnly() {
|
|
547
|
+
const recv = () => this.recv();
|
|
548
|
+
const getLen = () => this.len;
|
|
549
|
+
const getCap = () => this.cap;
|
|
550
|
+
const getIter = () => this[Symbol.asyncIterator]();
|
|
551
|
+
return {
|
|
552
|
+
recv,
|
|
553
|
+
get len() {
|
|
554
|
+
return getLen();
|
|
555
|
+
},
|
|
556
|
+
get cap() {
|
|
557
|
+
return getCap();
|
|
558
|
+
},
|
|
559
|
+
[Symbol.asyncIterator]() {
|
|
560
|
+
return getIter();
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
495
564
|
async *[Symbol.asyncIterator]() {
|
|
496
565
|
while (true) {
|
|
497
566
|
const value = await this.recv();
|
|
@@ -510,7 +579,9 @@ function getChannelById(id) {
|
|
|
510
579
|
return channelRegistry.get(id);
|
|
511
580
|
}
|
|
512
581
|
function getChannelId(channel) {
|
|
513
|
-
|
|
582
|
+
const impl = channel;
|
|
583
|
+
impl._shared = true;
|
|
584
|
+
return impl._id;
|
|
514
585
|
}
|
|
515
586
|
|
|
516
587
|
// src/adapters/inline.ts
|
|
@@ -522,6 +593,7 @@ var InlineManagedWorker = class {
|
|
|
522
593
|
exitHandlers = [];
|
|
523
594
|
terminated = false;
|
|
524
595
|
cancelledTasks = /* @__PURE__ */ new Set();
|
|
596
|
+
fnCache = /* @__PURE__ */ new Map();
|
|
525
597
|
constructor() {
|
|
526
598
|
this.id = ++inlineIdCounter;
|
|
527
599
|
queueMicrotask(() => {
|
|
@@ -531,7 +603,7 @@ var InlineManagedWorker = class {
|
|
|
531
603
|
postMessage(msg) {
|
|
532
604
|
if (this.terminated) return;
|
|
533
605
|
if (msg.type === "execute") {
|
|
534
|
-
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels);
|
|
606
|
+
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels, msg.args);
|
|
535
607
|
} else if (msg.type === "cancel") {
|
|
536
608
|
this.cancelledTasks.add(msg.taskId);
|
|
537
609
|
} else if (msg.type === "channel-result") {
|
|
@@ -592,7 +664,7 @@ var InlineManagedWorker = class {
|
|
|
592
664
|
}
|
|
593
665
|
return proxies;
|
|
594
666
|
}
|
|
595
|
-
executeTask(taskId, fnStr, concurrent, channels) {
|
|
667
|
+
executeTask(taskId, fnStr, concurrent, channels, args) {
|
|
596
668
|
queueMicrotask(async () => {
|
|
597
669
|
if (this.terminated) return;
|
|
598
670
|
if (concurrent && this.cancelledTasks.has(taskId)) {
|
|
@@ -600,14 +672,20 @@ var InlineManagedWorker = class {
|
|
|
600
672
|
return;
|
|
601
673
|
}
|
|
602
674
|
try {
|
|
675
|
+
let parsedFn = this.fnCache.get(fnStr);
|
|
676
|
+
if (!parsedFn) {
|
|
677
|
+
parsedFn = new Function("return (" + fnStr + ")")();
|
|
678
|
+
if (this.fnCache.size >= 1e3) this.fnCache.clear();
|
|
679
|
+
this.fnCache.set(fnStr, parsedFn);
|
|
680
|
+
}
|
|
603
681
|
let result;
|
|
604
|
-
if (
|
|
682
|
+
if (args) {
|
|
683
|
+
result = await parsedFn(...args);
|
|
684
|
+
} else if (channels) {
|
|
605
685
|
const proxies = this.buildChannelProxies(channels);
|
|
606
|
-
|
|
607
|
-
result = await fn(proxies);
|
|
686
|
+
result = await parsedFn(proxies);
|
|
608
687
|
} else {
|
|
609
|
-
|
|
610
|
-
result = await fn();
|
|
688
|
+
result = await parsedFn();
|
|
611
689
|
}
|
|
612
690
|
if (concurrent && this.cancelledTasks.has(taskId)) {
|
|
613
691
|
this.cancelledTasks.delete(taskId);
|
|
@@ -660,6 +738,8 @@ var WorkerPool = class {
|
|
|
660
738
|
totalCompleted = 0;
|
|
661
739
|
totalFailed = 0;
|
|
662
740
|
taskMap = /* @__PURE__ */ new Map();
|
|
741
|
+
// Per-worker deques for work-stealing strategy
|
|
742
|
+
workerDeques = /* @__PURE__ */ new Map();
|
|
663
743
|
constructor(config, adapter) {
|
|
664
744
|
this.config = config;
|
|
665
745
|
this.adapter = adapter;
|
|
@@ -697,6 +777,110 @@ var WorkerPool = class {
|
|
|
697
777
|
}
|
|
698
778
|
return void 0;
|
|
699
779
|
}
|
|
780
|
+
// --- Work-stealing helpers ---
|
|
781
|
+
getOrCreateDeque(worker) {
|
|
782
|
+
let deque = this.workerDeques.get(worker);
|
|
783
|
+
if (!deque) {
|
|
784
|
+
deque = { high: [], normal: [], low: [] };
|
|
785
|
+
this.workerDeques.set(worker, deque);
|
|
786
|
+
}
|
|
787
|
+
return deque;
|
|
788
|
+
}
|
|
789
|
+
dequeSize(worker) {
|
|
790
|
+
const deque = this.workerDeques.get(worker);
|
|
791
|
+
if (!deque) return 0;
|
|
792
|
+
return deque.high.length + deque.normal.length + deque.low.length;
|
|
793
|
+
}
|
|
794
|
+
enqueueToWorker(worker, task2) {
|
|
795
|
+
this.getOrCreateDeque(worker)[task2.priority].push(task2);
|
|
796
|
+
}
|
|
797
|
+
/** Pop from own deque — FIFO within each priority level. */
|
|
798
|
+
dequeueFromOwn(worker) {
|
|
799
|
+
const deque = this.workerDeques.get(worker);
|
|
800
|
+
if (!deque) return void 0;
|
|
801
|
+
return deque.high.shift() ?? deque.normal.shift() ?? deque.low.shift();
|
|
802
|
+
}
|
|
803
|
+
/** Steal from a victim's deque — takes lowest-priority work from the back. */
|
|
804
|
+
stealFrom(victim) {
|
|
805
|
+
const deque = this.workerDeques.get(victim);
|
|
806
|
+
if (!deque) return void 0;
|
|
807
|
+
return deque.low.pop() ?? deque.normal.pop() ?? deque.high.pop();
|
|
808
|
+
}
|
|
809
|
+
/** Find the exclusive worker with the shortest deque to push a new task to. */
|
|
810
|
+
findShortestDequeWorker() {
|
|
811
|
+
let best;
|
|
812
|
+
let bestSize = Infinity;
|
|
813
|
+
const seen = /* @__PURE__ */ new Set();
|
|
814
|
+
for (const worker of this.exclusiveWorkers.values()) {
|
|
815
|
+
if (seen.has(worker)) continue;
|
|
816
|
+
seen.add(worker);
|
|
817
|
+
const size = this.dequeSize(worker);
|
|
818
|
+
if (size < bestSize) {
|
|
819
|
+
bestSize = size;
|
|
820
|
+
best = worker;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return best;
|
|
824
|
+
}
|
|
825
|
+
/** Steal a task from the busiest worker's deque, excluding the thief. */
|
|
826
|
+
stealFromBusiest(thief) {
|
|
827
|
+
let victim;
|
|
828
|
+
let maxSize = 0;
|
|
829
|
+
for (const [worker, deque] of this.workerDeques) {
|
|
830
|
+
if (worker === thief) continue;
|
|
831
|
+
const size = deque.high.length + deque.normal.length + deque.low.length;
|
|
832
|
+
if (size > maxSize) {
|
|
833
|
+
maxSize = size;
|
|
834
|
+
victim = worker;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (!victim || maxSize === 0) return void 0;
|
|
838
|
+
return this.stealFrom(victim);
|
|
839
|
+
}
|
|
840
|
+
/** Steal from any deque (no thief exclusion — used by resize). */
|
|
841
|
+
stealFromAny() {
|
|
842
|
+
let victim;
|
|
843
|
+
let maxSize = 0;
|
|
844
|
+
for (const [worker, deque] of this.workerDeques) {
|
|
845
|
+
const size = deque.high.length + deque.normal.length + deque.low.length;
|
|
846
|
+
if (size > maxSize) {
|
|
847
|
+
maxSize = size;
|
|
848
|
+
victim = worker;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (!victim || maxSize === 0) return void 0;
|
|
852
|
+
return this.stealFrom(victim);
|
|
853
|
+
}
|
|
854
|
+
/** Remove a task by ID from any worker's deque. */
|
|
855
|
+
removeFromDeques(taskId) {
|
|
856
|
+
for (const [, deque] of this.workerDeques) {
|
|
857
|
+
for (const priority of ["high", "normal", "low"]) {
|
|
858
|
+
const queue = deque[priority];
|
|
859
|
+
const idx = queue.findIndex((t) => t.id === taskId);
|
|
860
|
+
if (idx !== -1) {
|
|
861
|
+
return queue.splice(idx, 1)[0];
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return void 0;
|
|
866
|
+
}
|
|
867
|
+
/** Flush a worker's deque back to the global queue (for redistribution). */
|
|
868
|
+
flushDeque(worker) {
|
|
869
|
+
const deque = this.workerDeques.get(worker);
|
|
870
|
+
if (!deque) return;
|
|
871
|
+
for (const priority of ["high", "normal", "low"]) {
|
|
872
|
+
for (const task2 of deque[priority]) {
|
|
873
|
+
this.queues[priority].push(task2);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
this.workerDeques.delete(worker);
|
|
877
|
+
}
|
|
878
|
+
/** Clean up a deque if it's empty. */
|
|
879
|
+
cleanupDeque(worker) {
|
|
880
|
+
if (this.dequeSize(worker) === 0) {
|
|
881
|
+
this.workerDeques.delete(worker);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
700
884
|
// --- Submit ---
|
|
701
885
|
submit(task2) {
|
|
702
886
|
if (this.draining) {
|
|
@@ -722,6 +906,13 @@ var WorkerPool = class {
|
|
|
722
906
|
this.createAndReadyWorker();
|
|
723
907
|
return;
|
|
724
908
|
}
|
|
909
|
+
if (this.config.strategy === "work-stealing") {
|
|
910
|
+
const target = this.findShortestDequeWorker();
|
|
911
|
+
if (target) {
|
|
912
|
+
this.enqueueToWorker(target, task2);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
725
916
|
this.enqueue(task2);
|
|
726
917
|
}
|
|
727
918
|
submitConcurrent(task2) {
|
|
@@ -759,6 +950,7 @@ var WorkerPool = class {
|
|
|
759
950
|
type: "execute",
|
|
760
951
|
taskId: task2.id,
|
|
761
952
|
fnStr: task2.fnStr,
|
|
953
|
+
args: task2.args,
|
|
762
954
|
concurrent: false,
|
|
763
955
|
channels: task2.channels
|
|
764
956
|
};
|
|
@@ -780,6 +972,7 @@ var WorkerPool = class {
|
|
|
780
972
|
type: "execute",
|
|
781
973
|
taskId: task2.id,
|
|
782
974
|
fnStr: task2.fnStr,
|
|
975
|
+
args: task2.args,
|
|
783
976
|
concurrent: true,
|
|
784
977
|
channels: task2.channels
|
|
785
978
|
};
|
|
@@ -826,6 +1019,19 @@ var WorkerPool = class {
|
|
|
826
1019
|
}
|
|
827
1020
|
assignNextOrIdle(worker) {
|
|
828
1021
|
if (!this.allWorkers.has(worker)) return;
|
|
1022
|
+
if (this.config.strategy === "work-stealing") {
|
|
1023
|
+
const own = this.dequeueFromOwn(worker);
|
|
1024
|
+
if (own) {
|
|
1025
|
+
this.cleanupDeque(worker);
|
|
1026
|
+
this.dispatch(worker, own);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
const stolen = this.stealFromBusiest(worker);
|
|
1030
|
+
if (stolen) {
|
|
1031
|
+
this.dispatch(worker, stolen);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
829
1035
|
const next = this.dequeue();
|
|
830
1036
|
if (next) {
|
|
831
1037
|
this.dispatch(worker, next);
|
|
@@ -836,6 +1042,7 @@ var WorkerPool = class {
|
|
|
836
1042
|
this.dispatchConcurrent(worker, concurrentNext);
|
|
837
1043
|
return;
|
|
838
1044
|
}
|
|
1045
|
+
this.cleanupDeque(worker);
|
|
839
1046
|
this.makeIdle(worker);
|
|
840
1047
|
}
|
|
841
1048
|
assignNextConcurrentOrIdle(worker) {
|
|
@@ -855,6 +1062,13 @@ var WorkerPool = class {
|
|
|
855
1062
|
const exclusiveTask = this.dequeue();
|
|
856
1063
|
if (exclusiveTask) {
|
|
857
1064
|
this.dispatch(worker, exclusiveTask);
|
|
1065
|
+
} else if (this.config.strategy === "work-stealing") {
|
|
1066
|
+
const stolen = this.stealFromBusiest(worker);
|
|
1067
|
+
if (stolen) {
|
|
1068
|
+
this.dispatch(worker, stolen);
|
|
1069
|
+
} else {
|
|
1070
|
+
this.makeIdle(worker);
|
|
1071
|
+
}
|
|
858
1072
|
} else {
|
|
859
1073
|
this.makeIdle(worker);
|
|
860
1074
|
}
|
|
@@ -950,6 +1164,13 @@ var WorkerPool = class {
|
|
|
950
1164
|
removed.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
951
1165
|
return;
|
|
952
1166
|
}
|
|
1167
|
+
if (this.config.strategy === "work-stealing") {
|
|
1168
|
+
const removedFromDeque = this.removeFromDeques(taskId);
|
|
1169
|
+
if (removedFromDeque) {
|
|
1170
|
+
removedFromDeque.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
953
1174
|
const removedConcurrent = this.removeFromConcurrentQueue(taskId);
|
|
954
1175
|
if (removedConcurrent) {
|
|
955
1176
|
removedConcurrent.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
@@ -960,6 +1181,7 @@ var WorkerPool = class {
|
|
|
960
1181
|
this.exclusiveWorkers.delete(taskId);
|
|
961
1182
|
this.allWorkers.delete(exclusiveWorker);
|
|
962
1183
|
this.taskMap.delete(taskId);
|
|
1184
|
+
this.flushDeque(exclusiveWorker);
|
|
963
1185
|
exclusiveWorker.terminate();
|
|
964
1186
|
return;
|
|
965
1187
|
}
|
|
@@ -991,6 +1213,14 @@ var WorkerPool = class {
|
|
|
991
1213
|
}
|
|
992
1214
|
this.concurrentQueues[priority] = [];
|
|
993
1215
|
}
|
|
1216
|
+
for (const [, deque] of this.workerDeques) {
|
|
1217
|
+
for (const priority of ["high", "normal", "low"]) {
|
|
1218
|
+
for (const task2 of deque[priority]) {
|
|
1219
|
+
task2.reject(new Error("Pool is shutting down"));
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
this.workerDeques.clear();
|
|
994
1224
|
for (const [taskId] of this.exclusiveWorkers) {
|
|
995
1225
|
this.taskMap.delete(taskId);
|
|
996
1226
|
}
|
|
@@ -1018,7 +1248,7 @@ var WorkerPool = class {
|
|
|
1018
1248
|
while (true) {
|
|
1019
1249
|
const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
|
|
1020
1250
|
if (totalWorkers >= maxThreads) break;
|
|
1021
|
-
const task2 = this.dequeue() ?? this.dequeueConcurrent();
|
|
1251
|
+
const task2 = this.dequeue() ?? (this.config.strategy === "work-stealing" ? this.stealFromAny() : void 0) ?? this.dequeueConcurrent();
|
|
1022
1252
|
if (!task2) break;
|
|
1023
1253
|
this.pendingWorkerCount++;
|
|
1024
1254
|
this.pendingTasksForWorkers.push(task2);
|
|
@@ -1081,6 +1311,7 @@ var WorkerPool = class {
|
|
|
1081
1311
|
this.idleWorkers.splice(idleIdx, 1);
|
|
1082
1312
|
}
|
|
1083
1313
|
this.rejectExclusiveTaskForWorker(worker, new Error("Worker exited unexpectedly"));
|
|
1314
|
+
this.flushDeque(worker);
|
|
1084
1315
|
const taskSet = this.sharedWorkers.get(worker);
|
|
1085
1316
|
if (taskSet) {
|
|
1086
1317
|
for (const taskId of taskSet) {
|
|
@@ -1096,6 +1327,17 @@ var WorkerPool = class {
|
|
|
1096
1327
|
for (const taskSet of this.sharedWorkers.values()) {
|
|
1097
1328
|
concurrentTasks += taskSet.size;
|
|
1098
1329
|
}
|
|
1330
|
+
let dequeHigh = 0;
|
|
1331
|
+
let dequeNormal = 0;
|
|
1332
|
+
let dequeLow = 0;
|
|
1333
|
+
for (const deque of this.workerDeques.values()) {
|
|
1334
|
+
dequeHigh += deque.high.length;
|
|
1335
|
+
dequeNormal += deque.normal.length;
|
|
1336
|
+
dequeLow += deque.low.length;
|
|
1337
|
+
}
|
|
1338
|
+
const queuedHigh = this.queues.high.length + dequeHigh;
|
|
1339
|
+
const queuedNormal = this.queues.normal.length + dequeNormal;
|
|
1340
|
+
const queuedLow = this.queues.low.length + dequeLow;
|
|
1099
1341
|
return {
|
|
1100
1342
|
totalWorkers: this.allWorkers.size,
|
|
1101
1343
|
idleWorkers: this.idleWorkers.length,
|
|
@@ -1104,10 +1346,10 @@ var WorkerPool = class {
|
|
|
1104
1346
|
concurrentTasks,
|
|
1105
1347
|
pendingWorkers: this.pendingWorkerCount,
|
|
1106
1348
|
queuedTasks: {
|
|
1107
|
-
high:
|
|
1108
|
-
normal:
|
|
1109
|
-
low:
|
|
1110
|
-
total:
|
|
1349
|
+
high: queuedHigh,
|
|
1350
|
+
normal: queuedNormal,
|
|
1351
|
+
low: queuedLow,
|
|
1352
|
+
total: queuedHigh + queuedNormal + queuedLow
|
|
1111
1353
|
},
|
|
1112
1354
|
queuedConcurrentTasks: {
|
|
1113
1355
|
high: this.concurrentQueues.high.length,
|
|
@@ -1162,7 +1404,7 @@ var taskCounter = 0;
|
|
|
1162
1404
|
function spawn(fn, opts) {
|
|
1163
1405
|
const fnStr = serializeFunction(fn);
|
|
1164
1406
|
const taskId = String(++taskCounter);
|
|
1165
|
-
const
|
|
1407
|
+
const spawnError = new Error();
|
|
1166
1408
|
let resolveFn;
|
|
1167
1409
|
let rejectFn;
|
|
1168
1410
|
let settled = false;
|
|
@@ -1192,14 +1434,29 @@ function spawn(fn, opts) {
|
|
|
1192
1434
|
reject: (reason) => {
|
|
1193
1435
|
if (!settled) {
|
|
1194
1436
|
settled = true;
|
|
1195
|
-
if (reason instanceof Error
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1437
|
+
if (reason instanceof Error) {
|
|
1438
|
+
const spawnStack = spawnError.stack;
|
|
1439
|
+
if (spawnStack) {
|
|
1440
|
+
const callerLine = spawnStack.split("\n").slice(2).join("\n");
|
|
1441
|
+
reason.stack = (reason.stack ?? reason.message) + "\n --- spawned at ---\n" + callerLine;
|
|
1442
|
+
}
|
|
1198
1443
|
}
|
|
1199
1444
|
rejectFn(reason);
|
|
1200
1445
|
}
|
|
1201
1446
|
}
|
|
1202
1447
|
};
|
|
1448
|
+
const ctx = opts?.ctx;
|
|
1449
|
+
if (ctx) {
|
|
1450
|
+
if (ctx.signal.aborted) {
|
|
1451
|
+
settled = true;
|
|
1452
|
+
rejectFn(ctx.err ?? new DOMException("Task was cancelled", "AbortError"));
|
|
1453
|
+
return {
|
|
1454
|
+
result,
|
|
1455
|
+
cancel: () => {
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1203
1460
|
getPool().submit(task2);
|
|
1204
1461
|
const cancel = () => {
|
|
1205
1462
|
if (settled) return;
|
|
@@ -1207,6 +1464,14 @@ function spawn(fn, opts) {
|
|
|
1207
1464
|
getPool().cancelTask(taskId);
|
|
1208
1465
|
rejectFn(new DOMException("Task was cancelled", "AbortError"));
|
|
1209
1466
|
};
|
|
1467
|
+
if (ctx) {
|
|
1468
|
+
const onAbort = () => cancel();
|
|
1469
|
+
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
1470
|
+
result.then(
|
|
1471
|
+
() => ctx.signal.removeEventListener("abort", onAbort),
|
|
1472
|
+
() => ctx.signal.removeEventListener("abort", onAbort)
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1210
1475
|
return { result, cancel };
|
|
1211
1476
|
}
|
|
1212
1477
|
|
|
@@ -1214,6 +1479,17 @@ function spawn(fn, opts) {
|
|
|
1214
1479
|
var WaitGroup = class {
|
|
1215
1480
|
tasks = [];
|
|
1216
1481
|
controller = new AbortController();
|
|
1482
|
+
ctx;
|
|
1483
|
+
constructor(ctx) {
|
|
1484
|
+
this.ctx = ctx;
|
|
1485
|
+
if (ctx) {
|
|
1486
|
+
if (ctx.signal.aborted) {
|
|
1487
|
+
this.controller.abort();
|
|
1488
|
+
} else {
|
|
1489
|
+
ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1217
1493
|
/**
|
|
1218
1494
|
* An `AbortSignal` shared across all tasks in this group.
|
|
1219
1495
|
* Pass it into spawned functions so they can stop early when `cancel()` is called.
|
|
@@ -1230,7 +1506,7 @@ var WaitGroup = class {
|
|
|
1230
1506
|
if (this.controller.signal.aborted) {
|
|
1231
1507
|
throw new Error("WaitGroup has been cancelled");
|
|
1232
1508
|
}
|
|
1233
|
-
const handle = spawn(fn, opts);
|
|
1509
|
+
const handle = spawn(fn, { ...opts, ctx: this.ctx });
|
|
1234
1510
|
this.tasks.push(handle);
|
|
1235
1511
|
}
|
|
1236
1512
|
/**
|
|
@@ -1265,22 +1541,81 @@ var ErrGroup = class {
|
|
|
1265
1541
|
controller = new AbortController();
|
|
1266
1542
|
firstError = null;
|
|
1267
1543
|
hasError = false;
|
|
1544
|
+
ctx;
|
|
1545
|
+
limit = 0;
|
|
1546
|
+
// 0 = unlimited
|
|
1547
|
+
inFlight = 0;
|
|
1548
|
+
waiting = [];
|
|
1549
|
+
constructor(ctx) {
|
|
1550
|
+
this.ctx = ctx;
|
|
1551
|
+
if (ctx) {
|
|
1552
|
+
if (ctx.signal.aborted) {
|
|
1553
|
+
this.controller.abort();
|
|
1554
|
+
} else {
|
|
1555
|
+
ctx.signal.addEventListener("abort", () => this.cancel(), { once: true });
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1268
1559
|
get signal() {
|
|
1269
1560
|
return this.controller.signal;
|
|
1270
1561
|
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Set the maximum number of tasks that can run concurrently.
|
|
1564
|
+
* Like Go's `errgroup.SetLimit()`. Must be called before any `spawn()`.
|
|
1565
|
+
* A value of 0 (default) means unlimited.
|
|
1566
|
+
*/
|
|
1567
|
+
setLimit(n) {
|
|
1568
|
+
if (this.tasks.length > 0) {
|
|
1569
|
+
throw new Error("SetLimit must be called before any spawn()");
|
|
1570
|
+
}
|
|
1571
|
+
if (n < 0 || !Number.isInteger(n)) {
|
|
1572
|
+
throw new RangeError("Limit must be a non-negative integer");
|
|
1573
|
+
}
|
|
1574
|
+
this.limit = n;
|
|
1575
|
+
}
|
|
1271
1576
|
spawn(fn, opts) {
|
|
1272
1577
|
if (this.controller.signal.aborted) {
|
|
1273
1578
|
throw new Error("ErrGroup has been cancelled");
|
|
1274
1579
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1580
|
+
if (this.limit > 0 && this.inFlight >= this.limit) {
|
|
1581
|
+
let innerCancel = () => {
|
|
1582
|
+
};
|
|
1583
|
+
let cancelled = false;
|
|
1584
|
+
const cancel = () => {
|
|
1585
|
+
cancelled = true;
|
|
1586
|
+
innerCancel();
|
|
1587
|
+
};
|
|
1588
|
+
const result = new Promise((resolve) => {
|
|
1589
|
+
this.waiting.push(resolve);
|
|
1590
|
+
}).then(() => {
|
|
1591
|
+
if (cancelled) throw new DOMException("Task was cancelled", "AbortError");
|
|
1592
|
+
const handle2 = this.doSpawn(fn, opts);
|
|
1593
|
+
innerCancel = handle2.cancel;
|
|
1594
|
+
return handle2.result;
|
|
1595
|
+
});
|
|
1596
|
+
this.tasks.push({ result, cancel });
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
const handle = this.doSpawn(fn, opts);
|
|
1600
|
+
this.tasks.push(handle);
|
|
1601
|
+
}
|
|
1602
|
+
doSpawn(fn, opts) {
|
|
1603
|
+
this.inFlight++;
|
|
1604
|
+
const handle = spawn(fn, { ...opts, ctx: this.ctx });
|
|
1605
|
+
const onSettle = () => {
|
|
1606
|
+
this.inFlight--;
|
|
1607
|
+
const next = this.waiting.shift();
|
|
1608
|
+
if (next) next();
|
|
1609
|
+
};
|
|
1610
|
+
handle.result.then(onSettle, (err) => {
|
|
1611
|
+
onSettle();
|
|
1277
1612
|
if (!this.hasError) {
|
|
1278
1613
|
this.hasError = true;
|
|
1279
1614
|
this.firstError = err;
|
|
1280
1615
|
this.cancel();
|
|
1281
1616
|
}
|
|
1282
1617
|
});
|
|
1283
|
-
|
|
1618
|
+
return handle;
|
|
1284
1619
|
}
|
|
1285
1620
|
async wait() {
|
|
1286
1621
|
const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
|
|
@@ -1336,6 +1671,212 @@ var Mutex = class {
|
|
|
1336
1671
|
return this.locked;
|
|
1337
1672
|
}
|
|
1338
1673
|
};
|
|
1674
|
+
var RWMutex = class {
|
|
1675
|
+
readers = 0;
|
|
1676
|
+
writing = false;
|
|
1677
|
+
readQueue = [];
|
|
1678
|
+
writeQueue = [];
|
|
1679
|
+
async rLock() {
|
|
1680
|
+
if (!this.writing && this.writeQueue.length === 0) {
|
|
1681
|
+
this.readers++;
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
return new Promise((resolve) => {
|
|
1685
|
+
this.readQueue.push(() => {
|
|
1686
|
+
this.readers++;
|
|
1687
|
+
resolve();
|
|
1688
|
+
});
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
rUnlock() {
|
|
1692
|
+
if (this.readers <= 0) {
|
|
1693
|
+
throw new Error("Cannot rUnlock a RWMutex that is not read-locked");
|
|
1694
|
+
}
|
|
1695
|
+
this.readers--;
|
|
1696
|
+
if (this.readers === 0) {
|
|
1697
|
+
this.wakeWriter();
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
async lock() {
|
|
1701
|
+
if (!this.writing && this.readers === 0) {
|
|
1702
|
+
this.writing = true;
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
return new Promise((resolve) => {
|
|
1706
|
+
this.writeQueue.push(() => {
|
|
1707
|
+
this.writing = true;
|
|
1708
|
+
resolve();
|
|
1709
|
+
});
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
unlock() {
|
|
1713
|
+
if (!this.writing) {
|
|
1714
|
+
throw new Error("Cannot unlock a RWMutex that is not write-locked");
|
|
1715
|
+
}
|
|
1716
|
+
this.writing = false;
|
|
1717
|
+
if (this.readQueue.length > 0) {
|
|
1718
|
+
this.wakeReaders();
|
|
1719
|
+
} else {
|
|
1720
|
+
this.wakeWriter();
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
async withRLock(fn) {
|
|
1724
|
+
await this.rLock();
|
|
1725
|
+
try {
|
|
1726
|
+
return await fn();
|
|
1727
|
+
} finally {
|
|
1728
|
+
this.rUnlock();
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
async withLock(fn) {
|
|
1732
|
+
await this.lock();
|
|
1733
|
+
try {
|
|
1734
|
+
return await fn();
|
|
1735
|
+
} finally {
|
|
1736
|
+
this.unlock();
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
get isLocked() {
|
|
1740
|
+
return this.writing || this.readers > 0;
|
|
1741
|
+
}
|
|
1742
|
+
wakeReaders() {
|
|
1743
|
+
const queue = this.readQueue;
|
|
1744
|
+
this.readQueue = [];
|
|
1745
|
+
for (const wake of queue) {
|
|
1746
|
+
wake();
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
wakeWriter() {
|
|
1750
|
+
const next = this.writeQueue.shift();
|
|
1751
|
+
if (next) next();
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
// src/semaphore.ts
|
|
1756
|
+
var Semaphore = class {
|
|
1757
|
+
max;
|
|
1758
|
+
cur;
|
|
1759
|
+
queue = [];
|
|
1760
|
+
constructor(n) {
|
|
1761
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1762
|
+
throw new Error("Semaphore capacity must be a positive integer");
|
|
1763
|
+
}
|
|
1764
|
+
this.max = n;
|
|
1765
|
+
this.cur = 0;
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Acquire `n` permits, waiting if necessary until they are available.
|
|
1769
|
+
* Acquires are served in FIFO order.
|
|
1770
|
+
*/
|
|
1771
|
+
async acquire(n = 1) {
|
|
1772
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1773
|
+
throw new Error("Acquire count must be a positive integer");
|
|
1774
|
+
}
|
|
1775
|
+
if (n > this.max) {
|
|
1776
|
+
throw new Error(`Acquire count ${n} exceeds semaphore capacity ${this.max}`);
|
|
1777
|
+
}
|
|
1778
|
+
if (this.cur + n <= this.max && this.queue.length === 0) {
|
|
1779
|
+
this.cur += n;
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
return new Promise((resolve) => {
|
|
1783
|
+
this.queue.push({ n, resolve });
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Try to acquire `n` permits without blocking.
|
|
1788
|
+
* Returns `true` if successful, `false` if not enough permits are available.
|
|
1789
|
+
*/
|
|
1790
|
+
tryAcquire(n = 1) {
|
|
1791
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1792
|
+
throw new Error("Acquire count must be a positive integer");
|
|
1793
|
+
}
|
|
1794
|
+
if (n > this.max) {
|
|
1795
|
+
throw new Error(`Acquire count ${n} exceeds semaphore capacity ${this.max}`);
|
|
1796
|
+
}
|
|
1797
|
+
if (this.cur + n <= this.max && this.queue.length === 0) {
|
|
1798
|
+
this.cur += n;
|
|
1799
|
+
return true;
|
|
1800
|
+
}
|
|
1801
|
+
return false;
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Release `n` permits, potentially waking queued acquirers.
|
|
1805
|
+
*/
|
|
1806
|
+
release(n = 1) {
|
|
1807
|
+
if (n <= 0 || !Number.isInteger(n)) {
|
|
1808
|
+
throw new Error("Release count must be a positive integer");
|
|
1809
|
+
}
|
|
1810
|
+
if (this.cur - n < 0) {
|
|
1811
|
+
throw new Error("Released more permits than acquired");
|
|
1812
|
+
}
|
|
1813
|
+
this.cur -= n;
|
|
1814
|
+
this.wake();
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Acquire `n` permits, run `fn`, then release automatically — even if `fn` throws.
|
|
1818
|
+
*/
|
|
1819
|
+
async withAcquire(fn, n = 1) {
|
|
1820
|
+
await this.acquire(n);
|
|
1821
|
+
try {
|
|
1822
|
+
return await fn();
|
|
1823
|
+
} finally {
|
|
1824
|
+
this.release(n);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
/** Number of permits currently available. */
|
|
1828
|
+
get available() {
|
|
1829
|
+
return this.max - this.cur;
|
|
1830
|
+
}
|
|
1831
|
+
/** Total capacity of the semaphore. */
|
|
1832
|
+
get capacity() {
|
|
1833
|
+
return this.max;
|
|
1834
|
+
}
|
|
1835
|
+
wake() {
|
|
1836
|
+
while (this.queue.length > 0) {
|
|
1837
|
+
const head = this.queue[0];
|
|
1838
|
+
if (this.cur + head.n > this.max) break;
|
|
1839
|
+
this.queue.shift();
|
|
1840
|
+
this.cur += head.n;
|
|
1841
|
+
head.resolve();
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
// src/cond.ts
|
|
1847
|
+
var Cond = class {
|
|
1848
|
+
mu;
|
|
1849
|
+
waiters = [];
|
|
1850
|
+
constructor(mu) {
|
|
1851
|
+
this.mu = mu;
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Atomically releases the mutex, suspends the caller until `signal()` or `broadcast()`
|
|
1855
|
+
* is called, then re-acquires the mutex before returning.
|
|
1856
|
+
*
|
|
1857
|
+
* Must be called while holding the mutex.
|
|
1858
|
+
*/
|
|
1859
|
+
async wait() {
|
|
1860
|
+
this.mu.unlock();
|
|
1861
|
+
await new Promise((resolve) => {
|
|
1862
|
+
this.waiters.push(resolve);
|
|
1863
|
+
});
|
|
1864
|
+
await this.mu.lock();
|
|
1865
|
+
}
|
|
1866
|
+
/** Wake one waiting task (if any). */
|
|
1867
|
+
signal() {
|
|
1868
|
+
const next = this.waiters.shift();
|
|
1869
|
+
if (next) next();
|
|
1870
|
+
}
|
|
1871
|
+
/** Wake all waiting tasks. */
|
|
1872
|
+
broadcast() {
|
|
1873
|
+
const queue = this.waiters;
|
|
1874
|
+
this.waiters = [];
|
|
1875
|
+
for (const wake of queue) {
|
|
1876
|
+
wake();
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1339
1880
|
|
|
1340
1881
|
// src/once.ts
|
|
1341
1882
|
var Once = class {
|
|
@@ -1477,23 +2018,69 @@ function ticker(ms) {
|
|
|
1477
2018
|
return new Ticker(ms);
|
|
1478
2019
|
}
|
|
1479
2020
|
|
|
2021
|
+
// src/timer.ts
|
|
2022
|
+
var Timer = class {
|
|
2023
|
+
timer = null;
|
|
2024
|
+
_stopped = false;
|
|
2025
|
+
/** Promise that resolves when the timer fires. Replaced on `reset()`. */
|
|
2026
|
+
channel;
|
|
2027
|
+
constructor(ms) {
|
|
2028
|
+
this.channel = this.schedule(ms);
|
|
2029
|
+
}
|
|
2030
|
+
schedule(ms) {
|
|
2031
|
+
return new Promise((resolve) => {
|
|
2032
|
+
this.timer = setTimeout(() => {
|
|
2033
|
+
this._stopped = true;
|
|
2034
|
+
this.timer = null;
|
|
2035
|
+
resolve();
|
|
2036
|
+
}, ms);
|
|
2037
|
+
if (typeof this.timer === "object" && "unref" in this.timer) {
|
|
2038
|
+
this.timer.unref();
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Stop the timer. Returns `true` if the timer was pending (stopped before firing),
|
|
2044
|
+
* `false` if it had already fired or was already stopped.
|
|
2045
|
+
*
|
|
2046
|
+
* After stopping, the current `channel` promise will never resolve.
|
|
2047
|
+
*/
|
|
2048
|
+
stop() {
|
|
2049
|
+
if (this.timer === null) return false;
|
|
2050
|
+
clearTimeout(this.timer);
|
|
2051
|
+
this.timer = null;
|
|
2052
|
+
this._stopped = true;
|
|
2053
|
+
return true;
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Reset the timer to fire after `ms` milliseconds.
|
|
2057
|
+
* If the timer was pending, it is stopped first. Creates a new `channel` promise.
|
|
2058
|
+
*/
|
|
2059
|
+
reset(ms) {
|
|
2060
|
+
this.stop();
|
|
2061
|
+
this._stopped = false;
|
|
2062
|
+
this.channel = this.schedule(ms);
|
|
2063
|
+
}
|
|
2064
|
+
/** Whether the timer has fired or been stopped. */
|
|
2065
|
+
get stopped() {
|
|
2066
|
+
return this._stopped;
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
|
|
1480
2070
|
// src/registry.ts
|
|
1481
2071
|
var taskCounter2 = 0;
|
|
1482
2072
|
function task(fn) {
|
|
2073
|
+
const fnStr = serializeFunction(fn);
|
|
1483
2074
|
return (...args) => {
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
const json = JSON.stringify(a);
|
|
1487
|
-
if (json === void 0) {
|
|
2075
|
+
for (const a of args) {
|
|
2076
|
+
if (JSON.stringify(a) === void 0) {
|
|
1488
2077
|
throw new TypeError(
|
|
1489
2078
|
`Argument of type ${typeof a} is not JSON-serializable. task() args must be JSON-serializable (no undefined, functions, symbols, or BigInt).`
|
|
1490
2079
|
);
|
|
1491
2080
|
}
|
|
1492
|
-
|
|
1493
|
-
});
|
|
1494
|
-
const wrapperStr = `() => (${fnStr})(${serializedArgs.join(", ")})`;
|
|
2081
|
+
}
|
|
1495
2082
|
const taskId = `task_${++taskCounter2}`;
|
|
1496
|
-
const
|
|
2083
|
+
const spawnError = new Error();
|
|
1497
2084
|
let resolveFn;
|
|
1498
2085
|
let rejectFn;
|
|
1499
2086
|
const result = new Promise((resolve, reject) => {
|
|
@@ -1502,14 +2089,18 @@ function task(fn) {
|
|
|
1502
2089
|
});
|
|
1503
2090
|
const taskObj = {
|
|
1504
2091
|
id: taskId,
|
|
1505
|
-
fnStr
|
|
2092
|
+
fnStr,
|
|
2093
|
+
args,
|
|
1506
2094
|
priority: "normal",
|
|
1507
2095
|
concurrent: false,
|
|
1508
2096
|
resolve: (value) => resolveFn(value),
|
|
1509
2097
|
reject: (reason) => {
|
|
1510
|
-
if (reason instanceof Error
|
|
1511
|
-
const
|
|
1512
|
-
|
|
2098
|
+
if (reason instanceof Error) {
|
|
2099
|
+
const spawnStack = spawnError.stack;
|
|
2100
|
+
if (spawnStack) {
|
|
2101
|
+
const callerLine = spawnStack.split("\n").slice(2).join("\n");
|
|
2102
|
+
reason.stack = (reason.stack ?? reason.message) + "\n --- spawned at ---\n" + callerLine;
|
|
2103
|
+
}
|
|
1513
2104
|
}
|
|
1514
2105
|
rejectFn(reason);
|
|
1515
2106
|
}
|
|
@@ -1518,14 +2109,180 @@ function task(fn) {
|
|
|
1518
2109
|
return result;
|
|
1519
2110
|
};
|
|
1520
2111
|
}
|
|
2112
|
+
|
|
2113
|
+
// src/context.ts
|
|
2114
|
+
var CancelledError = class extends Error {
|
|
2115
|
+
constructor(message = "context cancelled") {
|
|
2116
|
+
super(message);
|
|
2117
|
+
this.name = "CancelledError";
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
var DeadlineExceededError = class extends Error {
|
|
2121
|
+
constructor() {
|
|
2122
|
+
super("context deadline exceeded");
|
|
2123
|
+
this.name = "DeadlineExceededError";
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
var BaseContext = class {
|
|
2127
|
+
_err = null;
|
|
2128
|
+
controller;
|
|
2129
|
+
parent;
|
|
2130
|
+
constructor(parent) {
|
|
2131
|
+
this.parent = parent;
|
|
2132
|
+
this.controller = new AbortController();
|
|
2133
|
+
if (parent) {
|
|
2134
|
+
if (parent.signal.aborted) {
|
|
2135
|
+
this._err = parent.err ?? new CancelledError();
|
|
2136
|
+
this.controller.abort();
|
|
2137
|
+
} else {
|
|
2138
|
+
parent.signal.addEventListener(
|
|
2139
|
+
"abort",
|
|
2140
|
+
() => {
|
|
2141
|
+
if (!this.controller.signal.aborted) {
|
|
2142
|
+
this._err = parent.err ?? new CancelledError();
|
|
2143
|
+
this.controller.abort();
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
{ once: true }
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
get signal() {
|
|
2152
|
+
return this.controller.signal;
|
|
2153
|
+
}
|
|
2154
|
+
get deadline() {
|
|
2155
|
+
return this.parent?.deadline ?? null;
|
|
2156
|
+
}
|
|
2157
|
+
get err() {
|
|
2158
|
+
return this._err;
|
|
2159
|
+
}
|
|
2160
|
+
value(_key) {
|
|
2161
|
+
return this.parent?.value(_key);
|
|
2162
|
+
}
|
|
2163
|
+
done() {
|
|
2164
|
+
if (this.controller.signal.aborted) return Promise.resolve();
|
|
2165
|
+
return new Promise((resolve) => {
|
|
2166
|
+
this.controller.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
var BackgroundContext = class {
|
|
2171
|
+
_signal = new AbortController().signal;
|
|
2172
|
+
get signal() {
|
|
2173
|
+
return this._signal;
|
|
2174
|
+
}
|
|
2175
|
+
get deadline() {
|
|
2176
|
+
return null;
|
|
2177
|
+
}
|
|
2178
|
+
get err() {
|
|
2179
|
+
return null;
|
|
2180
|
+
}
|
|
2181
|
+
value(_key) {
|
|
2182
|
+
return void 0;
|
|
2183
|
+
}
|
|
2184
|
+
done() {
|
|
2185
|
+
return new Promise(() => {
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
var bg = null;
|
|
2190
|
+
function background() {
|
|
2191
|
+
if (!bg) bg = new BackgroundContext();
|
|
2192
|
+
return bg;
|
|
2193
|
+
}
|
|
2194
|
+
var CancelContext = class extends BaseContext {
|
|
2195
|
+
cancel(reason) {
|
|
2196
|
+
if (!this.controller.signal.aborted) {
|
|
2197
|
+
this._err = new CancelledError(reason ?? "context cancelled");
|
|
2198
|
+
this.controller.abort();
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
function withCancel(parent) {
|
|
2203
|
+
const ctx = new CancelContext(parent);
|
|
2204
|
+
return [ctx, (reason) => ctx.cancel(reason)];
|
|
2205
|
+
}
|
|
2206
|
+
var DeadlineContext = class extends BaseContext {
|
|
2207
|
+
_deadline;
|
|
2208
|
+
timer = null;
|
|
2209
|
+
constructor(parent, deadline) {
|
|
2210
|
+
super(parent);
|
|
2211
|
+
this._deadline = deadline;
|
|
2212
|
+
if (parent.deadline && parent.deadline < deadline) {
|
|
2213
|
+
this._deadline = parent.deadline;
|
|
2214
|
+
}
|
|
2215
|
+
if (this.controller.signal.aborted) {
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
const ms = this._deadline.getTime() - Date.now();
|
|
2219
|
+
if (ms <= 0) {
|
|
2220
|
+
this._err = new DeadlineExceededError();
|
|
2221
|
+
this.controller.abort();
|
|
2222
|
+
} else {
|
|
2223
|
+
this.timer = setTimeout(() => {
|
|
2224
|
+
if (!this.controller.signal.aborted) {
|
|
2225
|
+
this._err = new DeadlineExceededError();
|
|
2226
|
+
this.controller.abort();
|
|
2227
|
+
}
|
|
2228
|
+
}, ms);
|
|
2229
|
+
if (typeof this.timer === "object" && "unref" in this.timer) {
|
|
2230
|
+
this.timer.unref();
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
get deadline() {
|
|
2235
|
+
return this._deadline;
|
|
2236
|
+
}
|
|
2237
|
+
cancel(reason) {
|
|
2238
|
+
if (this.timer !== null) {
|
|
2239
|
+
clearTimeout(this.timer);
|
|
2240
|
+
this.timer = null;
|
|
2241
|
+
}
|
|
2242
|
+
if (!this.controller.signal.aborted) {
|
|
2243
|
+
this._err = new CancelledError(reason ?? "context cancelled");
|
|
2244
|
+
this.controller.abort();
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
function withDeadline(parent, deadline) {
|
|
2249
|
+
const ctx = new DeadlineContext(parent, deadline);
|
|
2250
|
+
return [ctx, (reason) => ctx.cancel(reason)];
|
|
2251
|
+
}
|
|
2252
|
+
function withTimeout(parent, ms) {
|
|
2253
|
+
return withDeadline(parent, new Date(Date.now() + ms));
|
|
2254
|
+
}
|
|
2255
|
+
var ValueContext = class extends BaseContext {
|
|
2256
|
+
key;
|
|
2257
|
+
val;
|
|
2258
|
+
constructor(parent, key, val) {
|
|
2259
|
+
super(parent);
|
|
2260
|
+
this.key = key;
|
|
2261
|
+
this.val = val;
|
|
2262
|
+
}
|
|
2263
|
+
value(key) {
|
|
2264
|
+
if (key === this.key) return this.val;
|
|
2265
|
+
return this.parent?.value(key);
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
function withValue(parent, key, value) {
|
|
2269
|
+
return new ValueContext(parent, key, value);
|
|
2270
|
+
}
|
|
1521
2271
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1522
2272
|
0 && (module.exports = {
|
|
2273
|
+
CancelledError,
|
|
2274
|
+
Cond,
|
|
2275
|
+
DeadlineExceededError,
|
|
1523
2276
|
ErrGroup,
|
|
1524
2277
|
Mutex,
|
|
1525
2278
|
Once,
|
|
2279
|
+
RWMutex,
|
|
2280
|
+
Semaphore,
|
|
1526
2281
|
Ticker,
|
|
2282
|
+
Timer,
|
|
1527
2283
|
WaitGroup,
|
|
1528
2284
|
after,
|
|
2285
|
+
background,
|
|
1529
2286
|
chan,
|
|
1530
2287
|
configure,
|
|
1531
2288
|
detectCapability,
|
|
@@ -1536,6 +2293,10 @@ function task(fn) {
|
|
|
1536
2293
|
spawn,
|
|
1537
2294
|
stats,
|
|
1538
2295
|
task,
|
|
1539
|
-
ticker
|
|
2296
|
+
ticker,
|
|
2297
|
+
withCancel,
|
|
2298
|
+
withDeadline,
|
|
2299
|
+
withTimeout,
|
|
2300
|
+
withValue
|
|
1540
2301
|
});
|
|
1541
2302
|
//# sourceMappingURL=index.cjs.map
|