@atolljs/node 0.1.6 → 0.1.7

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.
@@ -24,6 +24,20 @@ function scoped(scope) {
24
24
  }
25
25
  var fmtBytes = (bytes) => bytes >= 1 << 20 ? `${(bytes / (1 << 20)).toFixed(1)}MB` : bytes >= 1024 ? `${(bytes / 1024).toFixed(1)}KB` : `${bytes}B`;
26
26
 
27
+ // ../../src/devtools.ts
28
+ var THREAD2 = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
29
+ var sink2 = null;
30
+ var emitDevtools = (event) => {
31
+ if (sink2) sink2({ ...event, at: performance.now(), thread: THREAD2 });
32
+ };
33
+ var forwardDevtools = (event) => {
34
+ if (sink2) sink2(event);
35
+ };
36
+ var devtoolsEnabled = () => sink2 !== null;
37
+ var BODY_PREVIEW = 8 * 1024;
38
+ var seq = 0;
39
+ var nextDevtoolsId = (prefix) => `${prefix}-${++seq}`;
40
+
27
41
  // ../../src/contract/sharedMemory.ts
28
42
  var memLog = scoped("memory");
29
43
  var codecTextEncoder = new TextEncoder();
@@ -246,8 +260,11 @@ function listFactory(d, ctx, byteOffset) {
246
260
  writeAt,
247
261
  commit: () => {
248
262
  if (ctx.version) {
249
- Atomics.add(ctx.version.view, ctx.version.index, 1);
263
+ const prev = Atomics.add(ctx.version.view, ctx.version.index, 1);
250
264
  Atomics.notify(ctx.version.view, ctx.version.index);
265
+ if (ctx.path && devtoolsEnabled()) {
266
+ emitDevtools({ type: "memory:write", path: ctx.path, version: prev + 1 });
267
+ }
251
268
  }
252
269
  },
253
270
  read: () => Array.from({ length: count }, (_, i) => readAt(i)),
@@ -384,6 +401,10 @@ var WorkerPoolImpl = class {
384
401
  waitMs: agg(),
385
402
  runMs: agg()
386
403
  };
404
+ /** Devtools correlation id ('pool-N') — stamped on every event this pool emits. */
405
+ poolId = nextDevtoolsId("pool");
406
+ label;
407
+ nextCallId = 0;
387
408
  sharedMemory;
388
409
  /** The attached memory-persistence adapter, if the config supplied one. */
389
410
  persistence;
@@ -404,6 +425,7 @@ var WorkerPoolImpl = class {
404
425
  this.maxQueue = config.maxQueue ?? Infinity;
405
426
  this.respawn = config.respawn ?? true;
406
427
  this.taskTimeout = config.taskTimeout;
428
+ this.label = config.name;
407
429
  if (config.sharedMemory) {
408
430
  this.sharedMemory = config.sharedMemory;
409
431
  this.memoryManager = new MemoryManager(config.memory);
@@ -418,6 +440,14 @@ var WorkerPoolImpl = class {
418
440
  { workerUrl: config.workerUrl ? String(config.workerUrl) : "createWorker()" }
419
441
  );
420
442
  poolLog.debug(`tip: setLogLevel('trace') for per-task detail`);
443
+ emitDevtools({
444
+ type: "pool:init",
445
+ poolId: this.poolId,
446
+ label: this.label,
447
+ poolSize,
448
+ concurrency: this.concurrency,
449
+ memoryBytes: this.sharedMemory?.totalBytes
450
+ });
421
451
  for (let i = 0; i < poolSize; i++) {
422
452
  this.addWorker(this.spawn());
423
453
  }
@@ -437,18 +467,27 @@ var WorkerPoolImpl = class {
437
467
  addWorker(worker) {
438
468
  const slot = { worker, inFlight: 0 };
439
469
  worker.addEventListener("message", (event) => {
440
- this.handleReply(slot, event.data);
470
+ const data = event.data;
471
+ if (data.type === "ATOLL_DEVTOOLS" && data.event) {
472
+ forwardDevtools({
473
+ ...data.event,
474
+ worker: { poolId: this.poolId, slot: this.slots.indexOf(slot) }
475
+ });
476
+ return;
477
+ }
478
+ this.handleReply(slot, data);
441
479
  });
442
480
  worker.addEventListener("error", (event) => {
443
481
  this.handleWorkerError(slot, event);
444
482
  });
445
483
  if (this.memoryManager) {
446
- worker.postMessage({ type: "INIT_MEMORY", memory: this.memoryManager.memory });
484
+ worker.postMessage({ type: "INIT_MEMORY", memory: this.memoryManager.memory, devtools: devtoolsEnabled() });
447
485
  } else {
448
- worker.postMessage({ type: "INIT" });
486
+ worker.postMessage({ type: "INIT", devtools: devtoolsEnabled() });
449
487
  }
450
488
  this.slots.push(slot);
451
489
  poolLog.debug(`worker spawned, ${this.memoryManager ? "INIT_MEMORY" : "INIT"} sent`);
490
+ emitDevtools({ type: "worker:spawn", poolId: this.poolId, slot: this.slots.indexOf(slot) });
452
491
  return slot;
453
492
  }
454
493
  handleReply(slot, data) {
@@ -459,14 +498,17 @@ var WorkerPoolImpl = class {
459
498
  this.detach(entry);
460
499
  if (!entry.done) {
461
500
  entry.done = true;
462
- this.statsAgg.runMs.add(performance.now() - (entry.sentAt ?? entry.enqueuedAt));
501
+ const runMs = performance.now() - (entry.sentAt ?? entry.enqueuedAt);
502
+ this.statsAgg.runMs.add(runMs);
463
503
  if (data.success) {
464
504
  this.statsAgg.completed++;
465
505
  taskLog.debug(`\u2190 ${entry.contract.taskId} ok`);
506
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "ok", runMs });
466
507
  entry.resolve(data.result);
467
508
  } else {
468
509
  this.statsAgg.failed++;
469
510
  taskLog.warn(`\u2190 ${entry.contract.taskId} failed: ${data.error}`);
511
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "error", runMs, error: data.error });
470
512
  entry.reject(new Error(data.error));
471
513
  }
472
514
  }
@@ -478,6 +520,8 @@ var WorkerPoolImpl = class {
478
520
  const e = event;
479
521
  const message = e?.message ?? (e?.error ? String(e.error) : "worker error");
480
522
  poolLog.warn(`worker error: ${message}`);
523
+ const idx = this.slots.indexOf(slot);
524
+ emitDevtools({ type: "worker:error", poolId: this.poolId, slot: idx, message });
481
525
  for (const [id, entry] of [...this.pending]) {
482
526
  if (entry.slot === slot) {
483
527
  this.pending.delete(id);
@@ -485,20 +529,22 @@ var WorkerPoolImpl = class {
485
529
  if (entry.done) continue;
486
530
  entry.done = true;
487
531
  this.statsAgg.failed++;
532
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: message });
488
533
  entry.reject(new WorkerCrashedError(message));
489
534
  }
490
535
  }
491
- const idx = this.slots.indexOf(slot);
492
536
  if (idx >= 0) this.slots.splice(idx, 1);
493
537
  slot.worker.terminate();
494
538
  if (this.respawn) {
495
539
  poolLog.info("respawning crashed worker");
496
540
  this.addWorker(this.spawn());
541
+ emitDevtools({ type: "worker:respawn", poolId: this.poolId, slot: this.slots.length - 1 });
497
542
  } else if (this.slots.length === 0) {
498
543
  for (const entry of this.queue.splice(0)) {
499
544
  entry.done = true;
500
545
  this.detach(entry);
501
546
  this.statsAgg.failed++;
547
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: message });
502
548
  entry.reject(new WorkerCrashedError(message));
503
549
  }
504
550
  }
@@ -527,10 +573,13 @@ var WorkerPoolImpl = class {
527
573
  entry.messageId = messageId;
528
574
  entry.slot = slot;
529
575
  entry.sentAt = performance.now();
530
- this.statsAgg.waitMs.add(performance.now() - entry.enqueuedAt);
576
+ const waitMs = entry.sentAt - entry.enqueuedAt;
577
+ this.statsAgg.waitMs.add(waitMs);
531
578
  slot.inFlight++;
532
579
  this.pending.set(messageId, entry);
533
- taskLog.debug(`\u2192 ${entry.contract.taskId}`, { worker: this.slots.indexOf(slot), args: entry.args });
580
+ const slotIndex = this.slots.indexOf(slot);
581
+ taskLog.debug(`\u2192 ${entry.contract.taskId}`, { worker: slotIndex, args: entry.args });
582
+ emitDevtools({ type: "task:dispatch", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, slot: slotIndex, waitMs });
534
583
  slot.worker.postMessage({
535
584
  type: "EXECUTE_TASK",
536
585
  messageId,
@@ -557,6 +606,7 @@ var WorkerPoolImpl = class {
557
606
  entry.done = true;
558
607
  this.detach(entry);
559
608
  this.statsAgg.aborted++;
609
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: err instanceof TaskTimeoutError ? "timeout" : "aborted" });
560
610
  entry.reject(err);
561
611
  this.drainQueue();
562
612
  this.checkDrained();
@@ -582,6 +632,7 @@ var WorkerPoolImpl = class {
582
632
  const entry = {
583
633
  contract,
584
634
  args,
635
+ callId: ++this.nextCallId,
585
636
  enqueuedAt: performance.now(),
586
637
  resolve,
587
638
  reject,
@@ -596,12 +647,14 @@ var WorkerPoolImpl = class {
596
647
  if (timeout !== void 0) {
597
648
  entry.timer = setTimeout(() => this.cancel(entry, new TaskTimeoutError()), timeout);
598
649
  }
650
+ emitDevtools({ type: "task:enqueue", poolId: this.poolId, callId: entry.callId, taskId: contract.taskId });
599
651
  const slot = this.pickWorker();
600
652
  if (slot) {
601
653
  this.send(slot, entry);
602
654
  } else if (this.queue.length >= this.maxQueue) {
603
655
  entry.done = true;
604
656
  this.detach(entry);
657
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: contract.taskId, outcome: "queue-full" });
605
658
  reject(new PoolQueueFullError());
606
659
  return;
607
660
  } else {
@@ -666,12 +719,14 @@ var WorkerPoolImpl = class {
666
719
  (e) => poolLog.warn("memory persistence stop failed", e)
667
720
  );
668
721
  poolLog.info(`terminating ${this.slots.length} worker(s)`);
722
+ emitDevtools({ type: "pool:terminate", poolId: this.poolId });
669
723
  const crashed = new WorkerCrashedError("pool terminated");
670
724
  for (const [, entry] of this.pending) {
671
725
  this.detach(entry);
672
726
  if (entry.done) continue;
673
727
  entry.done = true;
674
728
  this.statsAgg.failed++;
729
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: "pool terminated" });
675
730
  entry.reject(crashed);
676
731
  }
677
732
  this.pending.clear();
@@ -679,6 +734,7 @@ var WorkerPoolImpl = class {
679
734
  entry.done = true;
680
735
  this.detach(entry);
681
736
  this.statsAgg.failed++;
737
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: "pool terminated" });
682
738
  entry.reject(crashed);
683
739
  }
684
740
  for (const slot of this.slots) {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  WorkerPool,
3
3
  bindSharedMemories
4
- } from "./chunk-QD4C3PWH.js";
4
+ } from "./chunk-SHWEILEH.js";
5
5
 
6
6
  // src/worker.ts
7
7
  import { Worker as NodeWorker } from "node:worker_threads";
package/dist/redis.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scoped
3
- } from "./chunk-QD4C3PWH.js";
3
+ } from "./chunk-SHWEILEH.js";
4
4
 
5
5
  // src/redis.ts
6
6
  var log = scoped("redis-mem");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolljs/node",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Node runtime adapter — node:worker_threads pools, no framework",
5
5
  "homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "registry": "https://registry.npmjs.org"
35
35
  },
36
36
  "peerDependencies": {
37
- "@atolljs/core": "0.1.6"
37
+ "@atolljs/core": "0.1.7"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",