@dmop/puru 0.1.11 → 0.1.13
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 +2 -2
- package/README.md +11 -2
- package/dist/index.cjs +348 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -6
- package/dist/index.d.ts +77 -6
- package/dist/index.js +347 -52
- package/dist/index.js.map +1 -1
- package/llms-full.txt +2 -2
- package/llms.txt +1 -1
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
Mutex: () => Mutex,
|
|
28
28
|
Once: () => Once,
|
|
29
29
|
RWMutex: () => RWMutex,
|
|
30
|
+
Semaphore: () => Semaphore,
|
|
30
31
|
Ticker: () => Ticker,
|
|
31
32
|
Timer: () => Timer,
|
|
32
33
|
WaitGroup: () => WaitGroup,
|
|
@@ -54,10 +55,13 @@ module.exports = __toCommonJS(index_exports);
|
|
|
54
55
|
var NATIVE_CODE_RE = /\[native code\]/;
|
|
55
56
|
var METHOD_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(/;
|
|
56
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();
|
|
57
59
|
function serializeFunction(fn) {
|
|
58
60
|
if (typeof fn !== "function") {
|
|
59
61
|
throw new TypeError("Expected a function");
|
|
60
62
|
}
|
|
63
|
+
const cached = serializeCache.get(fn);
|
|
64
|
+
if (cached) return cached;
|
|
61
65
|
const str = fn.toString();
|
|
62
66
|
if (typeof str !== "string" || str.length === 0) {
|
|
63
67
|
throw new TypeError(
|
|
@@ -79,6 +83,7 @@ function serializeFunction(fn) {
|
|
|
79
83
|
"Class methods cannot be serialized. Use an arrow function wrapper instead."
|
|
80
84
|
);
|
|
81
85
|
}
|
|
86
|
+
serializeCache.set(fn, str);
|
|
82
87
|
return str;
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -199,15 +204,23 @@ function __buildChannelProxies(channels) {
|
|
|
199
204
|
return proxies;
|
|
200
205
|
}
|
|
201
206
|
|
|
202
|
-
|
|
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
|
+
}
|
|
203
220
|
if (channels) {
|
|
204
|
-
|
|
205
|
-
const fn = new Function('__ch', 'return (' + fnStr + ')(__ch)');
|
|
206
|
-
return fn(__ch);
|
|
207
|
-
} else {
|
|
208
|
-
const fn = new Function('return (' + fnStr + ')()');
|
|
209
|
-
return fn();
|
|
221
|
+
return parsedFn(__buildChannelProxies(channels));
|
|
210
222
|
}
|
|
223
|
+
return parsedFn();
|
|
211
224
|
}
|
|
212
225
|
`;
|
|
213
226
|
var NODE_BOOTSTRAP_CODE = `
|
|
@@ -233,7 +246,7 @@ parentPort.on('message', async (msg) => {
|
|
|
233
246
|
return;
|
|
234
247
|
}
|
|
235
248
|
try {
|
|
236
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
249
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
237
250
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
238
251
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
239
252
|
}
|
|
@@ -252,7 +265,7 @@ parentPort.on('message', async (msg) => {
|
|
|
252
265
|
})();
|
|
253
266
|
} else {
|
|
254
267
|
try {
|
|
255
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
268
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
256
269
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
257
270
|
} catch (error) {
|
|
258
271
|
parentPort.postMessage({
|
|
@@ -293,7 +306,7 @@ self.onmessage = async (event) => {
|
|
|
293
306
|
return;
|
|
294
307
|
}
|
|
295
308
|
try {
|
|
296
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
309
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
297
310
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
298
311
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
299
312
|
}
|
|
@@ -312,7 +325,7 @@ self.onmessage = async (event) => {
|
|
|
312
325
|
})();
|
|
313
326
|
} else {
|
|
314
327
|
try {
|
|
315
|
-
const result = await __execFn(msg.fnStr, msg.channels);
|
|
328
|
+
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
316
329
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
317
330
|
} catch (error) {
|
|
318
331
|
self.postMessage({
|
|
@@ -399,17 +412,14 @@ var BunManagedWorker = class {
|
|
|
399
412
|
on(event, handler) {
|
|
400
413
|
if (event === "message") {
|
|
401
414
|
this.worker.addEventListener("message", (e) => {
|
|
402
|
-
;
|
|
403
415
|
handler(e.data);
|
|
404
416
|
});
|
|
405
417
|
} else if (event === "error") {
|
|
406
418
|
this.worker.addEventListener("error", (e) => {
|
|
407
|
-
;
|
|
408
419
|
handler(e.error ?? new Error(e.message));
|
|
409
420
|
});
|
|
410
421
|
} else if (event === "exit") {
|
|
411
422
|
this.worker.addEventListener("close", (e) => {
|
|
412
|
-
;
|
|
413
423
|
handler(e.code ?? 0);
|
|
414
424
|
});
|
|
415
425
|
}
|
|
@@ -439,6 +449,8 @@ var ChannelImpl = class {
|
|
|
439
449
|
// constraint: can't create channels of nullable type
|
|
440
450
|
/** @internal */
|
|
441
451
|
_id;
|
|
452
|
+
/** @internal — true once the channel ID has been sent to a worker */
|
|
453
|
+
_shared = false;
|
|
442
454
|
buffer = [];
|
|
443
455
|
capacity;
|
|
444
456
|
closed = false;
|
|
@@ -480,6 +492,7 @@ var ChannelImpl = class {
|
|
|
480
492
|
this.buffer.push(sender2.value);
|
|
481
493
|
sender2.resolve();
|
|
482
494
|
}
|
|
495
|
+
this.maybeUnregister();
|
|
483
496
|
return Promise.resolve(value);
|
|
484
497
|
}
|
|
485
498
|
const sender = this.sendQueue.shift();
|
|
@@ -488,6 +501,7 @@ var ChannelImpl = class {
|
|
|
488
501
|
return Promise.resolve(sender.value);
|
|
489
502
|
}
|
|
490
503
|
if (this.closed) {
|
|
504
|
+
this.maybeUnregister();
|
|
491
505
|
return Promise.resolve(null);
|
|
492
506
|
}
|
|
493
507
|
return new Promise((resolve) => {
|
|
@@ -508,6 +522,11 @@ var ChannelImpl = class {
|
|
|
508
522
|
}
|
|
509
523
|
this.sendQueue = [];
|
|
510
524
|
}
|
|
525
|
+
maybeUnregister() {
|
|
526
|
+
if (!this._shared && this.closed && this.buffer.length === 0 && this.recvQueue.length === 0) {
|
|
527
|
+
channelRegistry.delete(this._id);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
511
530
|
sendOnly() {
|
|
512
531
|
const send = (value) => this.send(value);
|
|
513
532
|
const close = () => this.close();
|
|
@@ -560,7 +579,9 @@ function getChannelById(id) {
|
|
|
560
579
|
return channelRegistry.get(id);
|
|
561
580
|
}
|
|
562
581
|
function getChannelId(channel) {
|
|
563
|
-
|
|
582
|
+
const impl = channel;
|
|
583
|
+
impl._shared = true;
|
|
584
|
+
return impl._id;
|
|
564
585
|
}
|
|
565
586
|
|
|
566
587
|
// src/adapters/inline.ts
|
|
@@ -572,6 +593,7 @@ var InlineManagedWorker = class {
|
|
|
572
593
|
exitHandlers = [];
|
|
573
594
|
terminated = false;
|
|
574
595
|
cancelledTasks = /* @__PURE__ */ new Set();
|
|
596
|
+
fnCache = /* @__PURE__ */ new Map();
|
|
575
597
|
constructor() {
|
|
576
598
|
this.id = ++inlineIdCounter;
|
|
577
599
|
queueMicrotask(() => {
|
|
@@ -581,7 +603,7 @@ var InlineManagedWorker = class {
|
|
|
581
603
|
postMessage(msg) {
|
|
582
604
|
if (this.terminated) return;
|
|
583
605
|
if (msg.type === "execute") {
|
|
584
|
-
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels);
|
|
606
|
+
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels, msg.args);
|
|
585
607
|
} else if (msg.type === "cancel") {
|
|
586
608
|
this.cancelledTasks.add(msg.taskId);
|
|
587
609
|
} else if (msg.type === "channel-result") {
|
|
@@ -642,7 +664,7 @@ var InlineManagedWorker = class {
|
|
|
642
664
|
}
|
|
643
665
|
return proxies;
|
|
644
666
|
}
|
|
645
|
-
executeTask(taskId, fnStr, concurrent, channels) {
|
|
667
|
+
executeTask(taskId, fnStr, concurrent, channels, args) {
|
|
646
668
|
queueMicrotask(async () => {
|
|
647
669
|
if (this.terminated) return;
|
|
648
670
|
if (concurrent && this.cancelledTasks.has(taskId)) {
|
|
@@ -650,14 +672,20 @@ var InlineManagedWorker = class {
|
|
|
650
672
|
return;
|
|
651
673
|
}
|
|
652
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
|
+
}
|
|
653
681
|
let result;
|
|
654
|
-
if (
|
|
682
|
+
if (args) {
|
|
683
|
+
result = await parsedFn(...args);
|
|
684
|
+
} else if (channels) {
|
|
655
685
|
const proxies = this.buildChannelProxies(channels);
|
|
656
|
-
|
|
657
|
-
result = await fn(proxies);
|
|
686
|
+
result = await parsedFn(proxies);
|
|
658
687
|
} else {
|
|
659
|
-
|
|
660
|
-
result = await fn();
|
|
688
|
+
result = await parsedFn();
|
|
661
689
|
}
|
|
662
690
|
if (concurrent && this.cancelledTasks.has(taskId)) {
|
|
663
691
|
this.cancelledTasks.delete(taskId);
|
|
@@ -710,6 +738,8 @@ var WorkerPool = class {
|
|
|
710
738
|
totalCompleted = 0;
|
|
711
739
|
totalFailed = 0;
|
|
712
740
|
taskMap = /* @__PURE__ */ new Map();
|
|
741
|
+
// Per-worker deques for work-stealing strategy
|
|
742
|
+
workerDeques = /* @__PURE__ */ new Map();
|
|
713
743
|
constructor(config, adapter) {
|
|
714
744
|
this.config = config;
|
|
715
745
|
this.adapter = adapter;
|
|
@@ -747,6 +777,110 @@ var WorkerPool = class {
|
|
|
747
777
|
}
|
|
748
778
|
return void 0;
|
|
749
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
|
+
}
|
|
750
884
|
// --- Submit ---
|
|
751
885
|
submit(task2) {
|
|
752
886
|
if (this.draining) {
|
|
@@ -772,6 +906,13 @@ var WorkerPool = class {
|
|
|
772
906
|
this.createAndReadyWorker();
|
|
773
907
|
return;
|
|
774
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
|
+
}
|
|
775
916
|
this.enqueue(task2);
|
|
776
917
|
}
|
|
777
918
|
submitConcurrent(task2) {
|
|
@@ -809,6 +950,7 @@ var WorkerPool = class {
|
|
|
809
950
|
type: "execute",
|
|
810
951
|
taskId: task2.id,
|
|
811
952
|
fnStr: task2.fnStr,
|
|
953
|
+
args: task2.args,
|
|
812
954
|
concurrent: false,
|
|
813
955
|
channels: task2.channels
|
|
814
956
|
};
|
|
@@ -830,6 +972,7 @@ var WorkerPool = class {
|
|
|
830
972
|
type: "execute",
|
|
831
973
|
taskId: task2.id,
|
|
832
974
|
fnStr: task2.fnStr,
|
|
975
|
+
args: task2.args,
|
|
833
976
|
concurrent: true,
|
|
834
977
|
channels: task2.channels
|
|
835
978
|
};
|
|
@@ -876,6 +1019,19 @@ var WorkerPool = class {
|
|
|
876
1019
|
}
|
|
877
1020
|
assignNextOrIdle(worker) {
|
|
878
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
|
+
}
|
|
879
1035
|
const next = this.dequeue();
|
|
880
1036
|
if (next) {
|
|
881
1037
|
this.dispatch(worker, next);
|
|
@@ -886,6 +1042,7 @@ var WorkerPool = class {
|
|
|
886
1042
|
this.dispatchConcurrent(worker, concurrentNext);
|
|
887
1043
|
return;
|
|
888
1044
|
}
|
|
1045
|
+
this.cleanupDeque(worker);
|
|
889
1046
|
this.makeIdle(worker);
|
|
890
1047
|
}
|
|
891
1048
|
assignNextConcurrentOrIdle(worker) {
|
|
@@ -905,6 +1062,13 @@ var WorkerPool = class {
|
|
|
905
1062
|
const exclusiveTask = this.dequeue();
|
|
906
1063
|
if (exclusiveTask) {
|
|
907
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
|
+
}
|
|
908
1072
|
} else {
|
|
909
1073
|
this.makeIdle(worker);
|
|
910
1074
|
}
|
|
@@ -1000,6 +1164,13 @@ var WorkerPool = class {
|
|
|
1000
1164
|
removed.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
1001
1165
|
return;
|
|
1002
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
|
+
}
|
|
1003
1174
|
const removedConcurrent = this.removeFromConcurrentQueue(taskId);
|
|
1004
1175
|
if (removedConcurrent) {
|
|
1005
1176
|
removedConcurrent.reject(new DOMException("Task was cancelled", "AbortError"));
|
|
@@ -1010,6 +1181,7 @@ var WorkerPool = class {
|
|
|
1010
1181
|
this.exclusiveWorkers.delete(taskId);
|
|
1011
1182
|
this.allWorkers.delete(exclusiveWorker);
|
|
1012
1183
|
this.taskMap.delete(taskId);
|
|
1184
|
+
this.flushDeque(exclusiveWorker);
|
|
1013
1185
|
exclusiveWorker.terminate();
|
|
1014
1186
|
return;
|
|
1015
1187
|
}
|
|
@@ -1041,6 +1213,14 @@ var WorkerPool = class {
|
|
|
1041
1213
|
}
|
|
1042
1214
|
this.concurrentQueues[priority] = [];
|
|
1043
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();
|
|
1044
1224
|
for (const [taskId] of this.exclusiveWorkers) {
|
|
1045
1225
|
this.taskMap.delete(taskId);
|
|
1046
1226
|
}
|
|
@@ -1068,7 +1248,7 @@ var WorkerPool = class {
|
|
|
1068
1248
|
while (true) {
|
|
1069
1249
|
const totalWorkers = this.allWorkers.size + this.pendingWorkerCount;
|
|
1070
1250
|
if (totalWorkers >= maxThreads) break;
|
|
1071
|
-
const task2 = this.dequeue() ?? this.dequeueConcurrent();
|
|
1251
|
+
const task2 = this.dequeue() ?? (this.config.strategy === "work-stealing" ? this.stealFromAny() : void 0) ?? this.dequeueConcurrent();
|
|
1072
1252
|
if (!task2) break;
|
|
1073
1253
|
this.pendingWorkerCount++;
|
|
1074
1254
|
this.pendingTasksForWorkers.push(task2);
|
|
@@ -1131,6 +1311,7 @@ var WorkerPool = class {
|
|
|
1131
1311
|
this.idleWorkers.splice(idleIdx, 1);
|
|
1132
1312
|
}
|
|
1133
1313
|
this.rejectExclusiveTaskForWorker(worker, new Error("Worker exited unexpectedly"));
|
|
1314
|
+
this.flushDeque(worker);
|
|
1134
1315
|
const taskSet = this.sharedWorkers.get(worker);
|
|
1135
1316
|
if (taskSet) {
|
|
1136
1317
|
for (const taskId of taskSet) {
|
|
@@ -1146,6 +1327,17 @@ var WorkerPool = class {
|
|
|
1146
1327
|
for (const taskSet of this.sharedWorkers.values()) {
|
|
1147
1328
|
concurrentTasks += taskSet.size;
|
|
1148
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;
|
|
1149
1341
|
return {
|
|
1150
1342
|
totalWorkers: this.allWorkers.size,
|
|
1151
1343
|
idleWorkers: this.idleWorkers.length,
|
|
@@ -1154,10 +1346,10 @@ var WorkerPool = class {
|
|
|
1154
1346
|
concurrentTasks,
|
|
1155
1347
|
pendingWorkers: this.pendingWorkerCount,
|
|
1156
1348
|
queuedTasks: {
|
|
1157
|
-
high:
|
|
1158
|
-
normal:
|
|
1159
|
-
low:
|
|
1160
|
-
total:
|
|
1349
|
+
high: queuedHigh,
|
|
1350
|
+
normal: queuedNormal,
|
|
1351
|
+
low: queuedLow,
|
|
1352
|
+
total: queuedHigh + queuedNormal + queuedLow
|
|
1161
1353
|
},
|
|
1162
1354
|
queuedConcurrentTasks: {
|
|
1163
1355
|
high: this.concurrentQueues.high.length,
|
|
@@ -1212,7 +1404,7 @@ var taskCounter = 0;
|
|
|
1212
1404
|
function spawn(fn, opts) {
|
|
1213
1405
|
const fnStr = serializeFunction(fn);
|
|
1214
1406
|
const taskId = String(++taskCounter);
|
|
1215
|
-
const
|
|
1407
|
+
const spawnError = new Error();
|
|
1216
1408
|
let resolveFn;
|
|
1217
1409
|
let rejectFn;
|
|
1218
1410
|
let settled = false;
|
|
@@ -1242,9 +1434,12 @@ function spawn(fn, opts) {
|
|
|
1242
1434
|
reject: (reason) => {
|
|
1243
1435
|
if (!settled) {
|
|
1244
1436
|
settled = true;
|
|
1245
|
-
if (reason instanceof Error
|
|
1246
|
-
const
|
|
1247
|
-
|
|
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
|
+
}
|
|
1248
1443
|
}
|
|
1249
1444
|
rejectFn(reason);
|
|
1250
1445
|
}
|
|
@@ -1383,16 +1578,26 @@ var ErrGroup = class {
|
|
|
1383
1578
|
throw new Error("ErrGroup has been cancelled");
|
|
1384
1579
|
}
|
|
1385
1580
|
if (this.limit > 0 && this.inFlight >= this.limit) {
|
|
1386
|
-
|
|
1581
|
+
let innerCancel = () => {
|
|
1582
|
+
};
|
|
1583
|
+
let cancelled = false;
|
|
1584
|
+
const cancel = () => {
|
|
1585
|
+
cancelled = true;
|
|
1586
|
+
innerCancel();
|
|
1587
|
+
};
|
|
1588
|
+
const result = new Promise((resolve) => {
|
|
1387
1589
|
this.waiting.push(resolve);
|
|
1388
|
-
}).then(() =>
|
|
1389
|
-
|
|
1390
|
-
|
|
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 });
|
|
1391
1597
|
return;
|
|
1392
1598
|
}
|
|
1393
|
-
const
|
|
1394
|
-
this.tasks.push(
|
|
1395
|
-
} });
|
|
1599
|
+
const handle = this.doSpawn(fn, opts);
|
|
1600
|
+
this.tasks.push(handle);
|
|
1396
1601
|
}
|
|
1397
1602
|
doSpawn(fn, opts) {
|
|
1398
1603
|
this.inFlight++;
|
|
@@ -1410,7 +1615,7 @@ var ErrGroup = class {
|
|
|
1410
1615
|
this.cancel();
|
|
1411
1616
|
}
|
|
1412
1617
|
});
|
|
1413
|
-
return handle
|
|
1618
|
+
return handle;
|
|
1414
1619
|
}
|
|
1415
1620
|
async wait() {
|
|
1416
1621
|
const settled = await Promise.allSettled(this.tasks.map((t) => t.result));
|
|
@@ -1547,6 +1752,97 @@ var RWMutex = class {
|
|
|
1547
1752
|
}
|
|
1548
1753
|
};
|
|
1549
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
|
+
|
|
1550
1846
|
// src/cond.ts
|
|
1551
1847
|
var Cond = class {
|
|
1552
1848
|
mu;
|
|
@@ -1619,7 +1915,6 @@ function select(cases, opts) {
|
|
|
1619
1915
|
if (settled) return;
|
|
1620
1916
|
settled = true;
|
|
1621
1917
|
try {
|
|
1622
|
-
;
|
|
1623
1918
|
handler(value);
|
|
1624
1919
|
resolve();
|
|
1625
1920
|
} catch (err) {
|
|
@@ -1653,7 +1948,6 @@ function select(cases, opts) {
|
|
|
1653
1948
|
if (settled) return;
|
|
1654
1949
|
settled = true;
|
|
1655
1950
|
try {
|
|
1656
|
-
;
|
|
1657
1951
|
handler(value);
|
|
1658
1952
|
resolve();
|
|
1659
1953
|
} catch (err) {
|
|
@@ -1778,18 +2072,15 @@ var taskCounter2 = 0;
|
|
|
1778
2072
|
function task(fn) {
|
|
1779
2073
|
const fnStr = serializeFunction(fn);
|
|
1780
2074
|
return (...args) => {
|
|
1781
|
-
const
|
|
1782
|
-
|
|
1783
|
-
if (json === void 0) {
|
|
2075
|
+
for (const a of args) {
|
|
2076
|
+
if (JSON.stringify(a) === void 0) {
|
|
1784
2077
|
throw new TypeError(
|
|
1785
2078
|
`Argument of type ${typeof a} is not JSON-serializable. task() args must be JSON-serializable (no undefined, functions, symbols, or BigInt).`
|
|
1786
2079
|
);
|
|
1787
2080
|
}
|
|
1788
|
-
|
|
1789
|
-
});
|
|
1790
|
-
const wrapperStr = `() => (${fnStr})(${serializedArgs.join(", ")})`;
|
|
2081
|
+
}
|
|
1791
2082
|
const taskId = `task_${++taskCounter2}`;
|
|
1792
|
-
const
|
|
2083
|
+
const spawnError = new Error();
|
|
1793
2084
|
let resolveFn;
|
|
1794
2085
|
let rejectFn;
|
|
1795
2086
|
const result = new Promise((resolve, reject) => {
|
|
@@ -1798,14 +2089,18 @@ function task(fn) {
|
|
|
1798
2089
|
});
|
|
1799
2090
|
const taskObj = {
|
|
1800
2091
|
id: taskId,
|
|
1801
|
-
fnStr
|
|
2092
|
+
fnStr,
|
|
2093
|
+
args,
|
|
1802
2094
|
priority: "normal",
|
|
1803
2095
|
concurrent: false,
|
|
1804
2096
|
resolve: (value) => resolveFn(value),
|
|
1805
2097
|
reject: (reason) => {
|
|
1806
|
-
if (reason instanceof Error
|
|
1807
|
-
const
|
|
1808
|
-
|
|
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
|
+
}
|
|
1809
2104
|
}
|
|
1810
2105
|
rejectFn(reason);
|
|
1811
2106
|
}
|
|
@@ -1982,6 +2277,7 @@ function withValue(parent, key, value) {
|
|
|
1982
2277
|
Mutex,
|
|
1983
2278
|
Once,
|
|
1984
2279
|
RWMutex,
|
|
2280
|
+
Semaphore,
|
|
1985
2281
|
Ticker,
|
|
1986
2282
|
Timer,
|
|
1987
2283
|
WaitGroup,
|