@dmop/puru 0.1.13 → 0.1.14
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/dist/index.cjs +142 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +142 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,10 @@ 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
5
|
var serializeCache = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
var functionRefCache = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
var functionIdCache = /* @__PURE__ */ new Map();
|
|
8
|
+
var FUNCTION_ID_CACHE_MAX = 512;
|
|
9
|
+
var functionIdCounter = 0;
|
|
6
10
|
function serializeFunction(fn) {
|
|
7
11
|
if (typeof fn !== "function") {
|
|
8
12
|
throw new TypeError("Expected a function");
|
|
@@ -33,6 +37,25 @@ function serializeFunction(fn) {
|
|
|
33
37
|
serializeCache.set(fn, str);
|
|
34
38
|
return str;
|
|
35
39
|
}
|
|
40
|
+
function getSerializedFunctionRef(fn) {
|
|
41
|
+
const cached = functionRefCache.get(fn);
|
|
42
|
+
if (cached) return cached;
|
|
43
|
+
const fnStr = serializeFunction(fn);
|
|
44
|
+
let fnId = functionIdCache.get(fnStr);
|
|
45
|
+
if (fnId) {
|
|
46
|
+
functionIdCache.delete(fnStr);
|
|
47
|
+
} else {
|
|
48
|
+
fnId = `fn_${++functionIdCounter}`;
|
|
49
|
+
}
|
|
50
|
+
functionIdCache.set(fnStr, fnId);
|
|
51
|
+
if (functionIdCache.size > FUNCTION_ID_CACHE_MAX) {
|
|
52
|
+
const oldestFnStr = functionIdCache.keys().next().value;
|
|
53
|
+
if (oldestFnStr !== void 0) functionIdCache.delete(oldestFnStr);
|
|
54
|
+
}
|
|
55
|
+
const ref = { fnId, fnStr };
|
|
56
|
+
functionRefCache.set(fn, ref);
|
|
57
|
+
return ref;
|
|
58
|
+
}
|
|
36
59
|
|
|
37
60
|
// src/configure.ts
|
|
38
61
|
import { availableParallelism } from "os";
|
|
@@ -154,12 +177,15 @@ function __buildChannelProxies(channels) {
|
|
|
154
177
|
const __fnCache = new Map();
|
|
155
178
|
const __FN_CACHE_MAX = 1000;
|
|
156
179
|
|
|
157
|
-
function __execFn(fnStr, channels, args) {
|
|
158
|
-
let parsedFn = __fnCache.get(
|
|
180
|
+
function __execFn(fnId, fnStr, channels, args) {
|
|
181
|
+
let parsedFn = __fnCache.get(fnId);
|
|
159
182
|
if (!parsedFn) {
|
|
183
|
+
if (!fnStr) {
|
|
184
|
+
throw new Error('Worker function was not registered on this worker');
|
|
185
|
+
}
|
|
160
186
|
parsedFn = (new Function('return (' + fnStr + ')'))();
|
|
161
187
|
if (__fnCache.size >= __FN_CACHE_MAX) __fnCache.delete(__fnCache.keys().next().value);
|
|
162
|
-
__fnCache.set(
|
|
188
|
+
__fnCache.set(fnId, parsedFn);
|
|
163
189
|
}
|
|
164
190
|
if (args) {
|
|
165
191
|
return parsedFn(...args);
|
|
@@ -193,7 +219,7 @@ parentPort.on('message', async (msg) => {
|
|
|
193
219
|
return;
|
|
194
220
|
}
|
|
195
221
|
try {
|
|
196
|
-
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
222
|
+
const result = await __execFn(msg.fnId, msg.fnStr, msg.channels, msg.args);
|
|
197
223
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
198
224
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
199
225
|
}
|
|
@@ -212,7 +238,7 @@ parentPort.on('message', async (msg) => {
|
|
|
212
238
|
})();
|
|
213
239
|
} else {
|
|
214
240
|
try {
|
|
215
|
-
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
241
|
+
const result = await __execFn(msg.fnId, msg.fnStr, msg.channels, msg.args);
|
|
216
242
|
parentPort.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
217
243
|
} catch (error) {
|
|
218
244
|
parentPort.postMessage({
|
|
@@ -253,7 +279,7 @@ self.onmessage = async (event) => {
|
|
|
253
279
|
return;
|
|
254
280
|
}
|
|
255
281
|
try {
|
|
256
|
-
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
282
|
+
const result = await __execFn(msg.fnId, msg.fnStr, msg.channels, msg.args);
|
|
257
283
|
if (!cancelledTasks.has(msg.taskId)) {
|
|
258
284
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
259
285
|
}
|
|
@@ -272,7 +298,7 @@ self.onmessage = async (event) => {
|
|
|
272
298
|
})();
|
|
273
299
|
} else {
|
|
274
300
|
try {
|
|
275
|
-
const result = await __execFn(msg.fnStr, msg.channels, msg.args);
|
|
301
|
+
const result = await __execFn(msg.fnId, msg.fnStr, msg.channels, msg.args);
|
|
276
302
|
self.postMessage({ type: 'result', taskId: msg.taskId, value: result });
|
|
277
303
|
} catch (error) {
|
|
278
304
|
self.postMessage({
|
|
@@ -388,8 +414,80 @@ var BunWorkerAdapter = class {
|
|
|
388
414
|
}
|
|
389
415
|
};
|
|
390
416
|
|
|
417
|
+
// src/queue.ts
|
|
418
|
+
var RingBuffer = class {
|
|
419
|
+
items;
|
|
420
|
+
head = 0;
|
|
421
|
+
tail = 0;
|
|
422
|
+
size = 0;
|
|
423
|
+
cap;
|
|
424
|
+
constructor(capacity) {
|
|
425
|
+
this.cap = capacity;
|
|
426
|
+
this.items = new Array(capacity);
|
|
427
|
+
}
|
|
428
|
+
get length() {
|
|
429
|
+
return this.size;
|
|
430
|
+
}
|
|
431
|
+
push(value) {
|
|
432
|
+
this.items[this.tail] = value;
|
|
433
|
+
this.tail = (this.tail + 1) % this.cap;
|
|
434
|
+
this.size++;
|
|
435
|
+
}
|
|
436
|
+
shift() {
|
|
437
|
+
if (this.size === 0) return void 0;
|
|
438
|
+
const value = this.items[this.head];
|
|
439
|
+
this.items[this.head] = void 0;
|
|
440
|
+
this.head = (this.head + 1) % this.cap;
|
|
441
|
+
this.size--;
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
var FifoQueue = class {
|
|
446
|
+
head = null;
|
|
447
|
+
tail = null;
|
|
448
|
+
size = 0;
|
|
449
|
+
get length() {
|
|
450
|
+
return this.size;
|
|
451
|
+
}
|
|
452
|
+
push(value) {
|
|
453
|
+
const node = { value, next: null };
|
|
454
|
+
if (this.tail) {
|
|
455
|
+
this.tail.next = node;
|
|
456
|
+
} else {
|
|
457
|
+
this.head = node;
|
|
458
|
+
}
|
|
459
|
+
this.tail = node;
|
|
460
|
+
this.size++;
|
|
461
|
+
}
|
|
462
|
+
shift() {
|
|
463
|
+
if (!this.head) return void 0;
|
|
464
|
+
const node = this.head;
|
|
465
|
+
this.head = node.next;
|
|
466
|
+
if (!this.head) this.tail = null;
|
|
467
|
+
this.size--;
|
|
468
|
+
const value = node.value;
|
|
469
|
+
node.value = void 0;
|
|
470
|
+
node.next = null;
|
|
471
|
+
return value;
|
|
472
|
+
}
|
|
473
|
+
clear() {
|
|
474
|
+
this.head = null;
|
|
475
|
+
this.tail = null;
|
|
476
|
+
this.size = 0;
|
|
477
|
+
}
|
|
478
|
+
*[Symbol.iterator]() {
|
|
479
|
+
let current = this.head;
|
|
480
|
+
while (current) {
|
|
481
|
+
yield current.value;
|
|
482
|
+
current = current.next;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
391
487
|
// src/channel.ts
|
|
392
488
|
var CLOSED = /* @__PURE__ */ Symbol("puru.channel.closed");
|
|
489
|
+
var RESOLVED_VOID = Promise.resolve();
|
|
490
|
+
var RESOLVED_NULL = Promise.resolve(null);
|
|
393
491
|
var channelIdCounter = 0;
|
|
394
492
|
var channelRegistry = /* @__PURE__ */ new Map();
|
|
395
493
|
var ChannelImpl = class {
|
|
@@ -398,14 +496,15 @@ var ChannelImpl = class {
|
|
|
398
496
|
_id;
|
|
399
497
|
/** @internal — true once the channel ID has been sent to a worker */
|
|
400
498
|
_shared = false;
|
|
401
|
-
buffer
|
|
499
|
+
buffer;
|
|
402
500
|
capacity;
|
|
403
501
|
closed = false;
|
|
404
|
-
recvQueue =
|
|
405
|
-
sendQueue =
|
|
502
|
+
recvQueue = new FifoQueue();
|
|
503
|
+
sendQueue = new FifoQueue();
|
|
406
504
|
constructor(capacity) {
|
|
407
505
|
this._id = `__ch_${++channelIdCounter}`;
|
|
408
506
|
this.capacity = capacity;
|
|
507
|
+
this.buffer = new RingBuffer(capacity);
|
|
409
508
|
channelRegistry.set(this._id, this);
|
|
410
509
|
}
|
|
411
510
|
get len() {
|
|
@@ -421,11 +520,11 @@ var ChannelImpl = class {
|
|
|
421
520
|
const receiver = this.recvQueue.shift();
|
|
422
521
|
if (receiver) {
|
|
423
522
|
receiver.resolve(value);
|
|
424
|
-
return
|
|
523
|
+
return RESOLVED_VOID;
|
|
425
524
|
}
|
|
426
525
|
if (this.buffer.length < this.capacity) {
|
|
427
526
|
this.buffer.push(value);
|
|
428
|
-
return
|
|
527
|
+
return RESOLVED_VOID;
|
|
429
528
|
}
|
|
430
529
|
return new Promise((resolve, reject) => {
|
|
431
530
|
this.sendQueue.push({ value, resolve, reject });
|
|
@@ -449,7 +548,7 @@ var ChannelImpl = class {
|
|
|
449
548
|
}
|
|
450
549
|
if (this.closed) {
|
|
451
550
|
this.maybeUnregister();
|
|
452
|
-
return
|
|
551
|
+
return RESOLVED_NULL;
|
|
453
552
|
}
|
|
454
553
|
return new Promise((resolve) => {
|
|
455
554
|
this.recvQueue.push({
|
|
@@ -463,11 +562,11 @@ var ChannelImpl = class {
|
|
|
463
562
|
for (const receiver of this.recvQueue) {
|
|
464
563
|
receiver.resolve(CLOSED);
|
|
465
564
|
}
|
|
466
|
-
this.recvQueue
|
|
565
|
+
this.recvQueue.clear();
|
|
467
566
|
for (const sender of this.sendQueue) {
|
|
468
567
|
sender.reject(new Error("send on closed channel"));
|
|
469
568
|
}
|
|
470
|
-
this.sendQueue
|
|
569
|
+
this.sendQueue.clear();
|
|
471
570
|
}
|
|
472
571
|
maybeUnregister() {
|
|
473
572
|
if (!this._shared && this.closed && this.buffer.length === 0 && this.recvQueue.length === 0) {
|
|
@@ -550,7 +649,7 @@ var InlineManagedWorker = class {
|
|
|
550
649
|
postMessage(msg) {
|
|
551
650
|
if (this.terminated) return;
|
|
552
651
|
if (msg.type === "execute") {
|
|
553
|
-
this.executeTask(msg.taskId, msg.fnStr, msg.concurrent, msg.channels, msg.args);
|
|
652
|
+
this.executeTask(msg.taskId, msg.fnId, msg.fnStr, msg.concurrent, msg.channels, msg.args);
|
|
554
653
|
} else if (msg.type === "cancel") {
|
|
555
654
|
this.cancelledTasks.add(msg.taskId);
|
|
556
655
|
} else if (msg.type === "channel-result") {
|
|
@@ -611,7 +710,7 @@ var InlineManagedWorker = class {
|
|
|
611
710
|
}
|
|
612
711
|
return proxies;
|
|
613
712
|
}
|
|
614
|
-
executeTask(taskId, fnStr, concurrent, channels, args) {
|
|
713
|
+
executeTask(taskId, fnId, fnStr, concurrent, channels, args) {
|
|
615
714
|
queueMicrotask(async () => {
|
|
616
715
|
if (this.terminated) return;
|
|
617
716
|
if (concurrent && this.cancelledTasks.has(taskId)) {
|
|
@@ -619,11 +718,14 @@ var InlineManagedWorker = class {
|
|
|
619
718
|
return;
|
|
620
719
|
}
|
|
621
720
|
try {
|
|
622
|
-
let parsedFn = this.fnCache.get(
|
|
721
|
+
let parsedFn = this.fnCache.get(fnId);
|
|
623
722
|
if (!parsedFn) {
|
|
723
|
+
if (!fnStr) {
|
|
724
|
+
throw new Error("Worker function was not registered on this worker");
|
|
725
|
+
}
|
|
624
726
|
parsedFn = new Function("return (" + fnStr + ")")();
|
|
625
727
|
if (this.fnCache.size >= 1e3) this.fnCache.clear();
|
|
626
|
-
this.fnCache.set(
|
|
728
|
+
this.fnCache.set(fnId, parsedFn);
|
|
627
729
|
}
|
|
628
730
|
let result;
|
|
629
731
|
if (args) {
|
|
@@ -685,6 +787,7 @@ var WorkerPool = class {
|
|
|
685
787
|
totalCompleted = 0;
|
|
686
788
|
totalFailed = 0;
|
|
687
789
|
taskMap = /* @__PURE__ */ new Map();
|
|
790
|
+
workerFunctionIds = /* @__PURE__ */ new Map();
|
|
688
791
|
// Per-worker deques for work-stealing strategy
|
|
689
792
|
workerDeques = /* @__PURE__ */ new Map();
|
|
690
793
|
constructor(config, adapter) {
|
|
@@ -896,7 +999,8 @@ var WorkerPool = class {
|
|
|
896
999
|
const msg = {
|
|
897
1000
|
type: "execute",
|
|
898
1001
|
taskId: task2.id,
|
|
899
|
-
|
|
1002
|
+
fnId: task2.fnId,
|
|
1003
|
+
fnStr: this.getWorkerFunctionString(worker, task2),
|
|
900
1004
|
args: task2.args,
|
|
901
1005
|
concurrent: false,
|
|
902
1006
|
channels: task2.channels
|
|
@@ -918,13 +1022,24 @@ var WorkerPool = class {
|
|
|
918
1022
|
const msg = {
|
|
919
1023
|
type: "execute",
|
|
920
1024
|
taskId: task2.id,
|
|
921
|
-
|
|
1025
|
+
fnId: task2.fnId,
|
|
1026
|
+
fnStr: this.getWorkerFunctionString(worker, task2),
|
|
922
1027
|
args: task2.args,
|
|
923
1028
|
concurrent: true,
|
|
924
1029
|
channels: task2.channels
|
|
925
1030
|
};
|
|
926
1031
|
worker.postMessage(msg);
|
|
927
1032
|
}
|
|
1033
|
+
getWorkerFunctionString(worker, task2) {
|
|
1034
|
+
let knownFunctions = this.workerFunctionIds.get(worker);
|
|
1035
|
+
if (!knownFunctions) {
|
|
1036
|
+
knownFunctions = /* @__PURE__ */ new Set();
|
|
1037
|
+
this.workerFunctionIds.set(worker, knownFunctions);
|
|
1038
|
+
}
|
|
1039
|
+
if (knownFunctions.has(task2.fnId)) return void 0;
|
|
1040
|
+
knownFunctions.add(task2.fnId);
|
|
1041
|
+
return task2.fnStr;
|
|
1042
|
+
}
|
|
928
1043
|
// --- Task completion ---
|
|
929
1044
|
handleWorkerMessage(worker, response) {
|
|
930
1045
|
if (response.type === "channel-op") {
|
|
@@ -1189,6 +1304,7 @@ var WorkerPool = class {
|
|
|
1189
1304
|
this.sharedWorkers.clear();
|
|
1190
1305
|
this.allWorkers.clear();
|
|
1191
1306
|
this.idleTimers.clear();
|
|
1307
|
+
this.workerFunctionIds.clear();
|
|
1192
1308
|
}
|
|
1193
1309
|
resize(maxThreads) {
|
|
1194
1310
|
this.config = { ...this.config, maxThreads };
|
|
@@ -1248,6 +1364,7 @@ var WorkerPool = class {
|
|
|
1248
1364
|
});
|
|
1249
1365
|
worker.on("exit", (_code) => {
|
|
1250
1366
|
this.allWorkers.delete(worker);
|
|
1367
|
+
this.workerFunctionIds.delete(worker);
|
|
1251
1368
|
const timer = this.idleTimers.get(worker);
|
|
1252
1369
|
if (timer) {
|
|
1253
1370
|
clearTimeout(timer);
|
|
@@ -1349,7 +1466,7 @@ async function shutdown() {
|
|
|
1349
1466
|
// src/spawn.ts
|
|
1350
1467
|
var taskCounter = 0;
|
|
1351
1468
|
function spawn(fn, opts) {
|
|
1352
|
-
const fnStr =
|
|
1469
|
+
const { fnId, fnStr } = getSerializedFunctionRef(fn);
|
|
1353
1470
|
const taskId = String(++taskCounter);
|
|
1354
1471
|
const spawnError = new Error();
|
|
1355
1472
|
let resolveFn;
|
|
@@ -1368,6 +1485,7 @@ function spawn(fn, opts) {
|
|
|
1368
1485
|
}
|
|
1369
1486
|
const task2 = {
|
|
1370
1487
|
id: taskId,
|
|
1488
|
+
fnId,
|
|
1371
1489
|
fnStr,
|
|
1372
1490
|
priority: opts?.priority ?? "normal",
|
|
1373
1491
|
concurrent: opts?.concurrent ?? false,
|
|
@@ -2017,7 +2135,7 @@ var Timer = class {
|
|
|
2017
2135
|
// src/registry.ts
|
|
2018
2136
|
var taskCounter2 = 0;
|
|
2019
2137
|
function task(fn) {
|
|
2020
|
-
const fnStr =
|
|
2138
|
+
const { fnId, fnStr } = getSerializedFunctionRef(fn);
|
|
2021
2139
|
return (...args) => {
|
|
2022
2140
|
for (const a of args) {
|
|
2023
2141
|
if (JSON.stringify(a) === void 0) {
|
|
@@ -2036,6 +2154,7 @@ function task(fn) {
|
|
|
2036
2154
|
});
|
|
2037
2155
|
const taskObj = {
|
|
2038
2156
|
id: taskId,
|
|
2157
|
+
fnId,
|
|
2039
2158
|
fnStr,
|
|
2040
2159
|
args,
|
|
2041
2160
|
priority: "normal",
|