@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.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(fnStr);
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(fnStr, parsedFn);
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 Promise.resolve();
523
+ return RESOLVED_VOID;
425
524
  }
426
525
  if (this.buffer.length < this.capacity) {
427
526
  this.buffer.push(value);
428
- return Promise.resolve();
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 Promise.resolve(null);
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(fnStr);
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(fnStr, parsedFn);
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
- fnStr: task2.fnStr,
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
- fnStr: task2.fnStr,
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 = serializeFunction(fn);
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 = serializeFunction(fn);
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",