@atolljs/node 0.1.6 → 0.1.8
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/{chunk-QD4C3PWH.js → chunk-VEJT6OVD.js} +381 -38
- package/dist/index.js +1 -1
- package/dist/redis.js +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,93 @@
|
|
|
1
|
+
// ../../src/devtools.ts
|
|
2
|
+
var THREAD = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
|
|
3
|
+
var sink = null;
|
|
4
|
+
var emitDevtools = (event) => {
|
|
5
|
+
if (sink) sink({ ...event, at: performance.now(), thread: THREAD });
|
|
6
|
+
};
|
|
7
|
+
var forwardDevtools = (event) => {
|
|
8
|
+
if (sink) sink(event);
|
|
9
|
+
};
|
|
10
|
+
var devtoolsEnabled = () => sink !== null;
|
|
11
|
+
var previewValue = (value, max = 2048) => {
|
|
12
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
13
|
+
let out;
|
|
14
|
+
try {
|
|
15
|
+
out = JSON.stringify(value, (_k, v) => {
|
|
16
|
+
if (typeof v === "function") return "[fn]";
|
|
17
|
+
if (typeof v === "bigint") return `${v}n`;
|
|
18
|
+
if (v instanceof ArrayBuffer) return `[ArrayBuffer ${v.byteLength}B]`;
|
|
19
|
+
if (ArrayBuffer.isView(v)) return `[${v.constructor.name} ${v.byteLength}B]`;
|
|
20
|
+
if (v instanceof Map) return { "[Map]": [...v.entries()].slice(0, 50) };
|
|
21
|
+
if (v instanceof Set) return { "[Set]": [...v].slice(0, 50) };
|
|
22
|
+
if (v && typeof v === "object") {
|
|
23
|
+
if (seen.has(v)) return "[cycle]";
|
|
24
|
+
seen.add(v);
|
|
25
|
+
}
|
|
26
|
+
return v;
|
|
27
|
+
}) ?? String(value);
|
|
28
|
+
} catch {
|
|
29
|
+
out = String(value);
|
|
30
|
+
}
|
|
31
|
+
return out.length > max ? `${out.slice(0, max)}\u2026` : out;
|
|
32
|
+
};
|
|
33
|
+
var estimateCloneBytes = (value, budget = 5e3) => {
|
|
34
|
+
let nodes = 0;
|
|
35
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
36
|
+
const walk = (v) => {
|
|
37
|
+
if (++nodes > budget) return 0;
|
|
38
|
+
switch (typeof v) {
|
|
39
|
+
case "string":
|
|
40
|
+
return 4 + v.length * 2;
|
|
41
|
+
case "number":
|
|
42
|
+
return 8;
|
|
43
|
+
case "boolean":
|
|
44
|
+
return 4;
|
|
45
|
+
case "bigint":
|
|
46
|
+
return 16;
|
|
47
|
+
case "undefined":
|
|
48
|
+
return 1;
|
|
49
|
+
case "object": {
|
|
50
|
+
if (v === null) return 1;
|
|
51
|
+
if (v instanceof ArrayBuffer) return v.byteLength;
|
|
52
|
+
if (ArrayBuffer.isView(v)) return v.byteLength;
|
|
53
|
+
if (typeof SharedArrayBuffer !== "undefined" && v instanceof SharedArrayBuffer) return 8;
|
|
54
|
+
if (seen.has(v)) return 4;
|
|
55
|
+
seen.add(v);
|
|
56
|
+
let n = 8;
|
|
57
|
+
if (v instanceof Map) for (const [k, x] of v) n += walk(k) + walk(x);
|
|
58
|
+
else if (v instanceof Set) for (const x of v) n += walk(x);
|
|
59
|
+
else if (Array.isArray(v)) for (const x of v) n += walk(x);
|
|
60
|
+
else for (const k of Object.keys(v)) n += 4 + k.length * 2 + walk(v[k]);
|
|
61
|
+
return n;
|
|
62
|
+
}
|
|
63
|
+
default:
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return walk(value);
|
|
68
|
+
};
|
|
69
|
+
var commands = /* @__PURE__ */ new Map();
|
|
70
|
+
var registerDevtoolsCommand = (name, handler) => {
|
|
71
|
+
commands.set(name, handler);
|
|
72
|
+
return () => {
|
|
73
|
+
if (commands.get(name) === handler) commands.delete(name);
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
var BODY_PREVIEW = 8 * 1024;
|
|
77
|
+
var seq = 0;
|
|
78
|
+
var named = /* @__PURE__ */ new Map();
|
|
79
|
+
var nextDevtoolsId = (prefix, name) => {
|
|
80
|
+
const slug = name?.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
81
|
+
if (!slug) return `${prefix}-${++seq}`;
|
|
82
|
+
const base = `${slug}-${prefix === "pool" ? "p" : prefix === "worker" ? "w" : prefix}`;
|
|
83
|
+
const n = (named.get(base) ?? 0) + 1;
|
|
84
|
+
named.set(base, n);
|
|
85
|
+
return n === 1 ? base : `${base}${n}`;
|
|
86
|
+
};
|
|
87
|
+
|
|
1
88
|
// ../../src/log.ts
|
|
2
89
|
var LEVELS = { trace: 0, debug: 1, info: 2, warn: 3, error: 4, off: 5 };
|
|
3
|
-
var
|
|
90
|
+
var THREAD2 = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
|
|
4
91
|
var consoleSink = ({ level, scope, message, data, thread, at }) => {
|
|
5
92
|
const prefix = `[${(at / 1e3).toFixed(2)}s ${thread}:${scope}]`;
|
|
6
93
|
const fn = level === "error" ? console.error : level === "warn" ? console.warn : level === "info" ? console.info : console.debug;
|
|
@@ -8,10 +95,25 @@ var consoleSink = ({ level, scope, message, data, thread, at }) => {
|
|
|
8
95
|
else fn(`${prefix} ${message}`);
|
|
9
96
|
};
|
|
10
97
|
var minLevel = "info";
|
|
11
|
-
var
|
|
98
|
+
var sink2 = consoleSink;
|
|
99
|
+
var teeing = false;
|
|
12
100
|
function log(level, scope, message, data) {
|
|
13
101
|
if (LEVELS[level] < LEVELS[minLevel]) return;
|
|
14
|
-
|
|
102
|
+
sink2({ level, scope, message, data, thread: THREAD2, at: performance.now() });
|
|
103
|
+
if (!teeing && devtoolsEnabled()) {
|
|
104
|
+
teeing = true;
|
|
105
|
+
try {
|
|
106
|
+
emitDevtools({
|
|
107
|
+
type: "log",
|
|
108
|
+
level,
|
|
109
|
+
scope,
|
|
110
|
+
message,
|
|
111
|
+
...data !== void 0 ? { data: previewValue(data, 1024) } : {}
|
|
112
|
+
});
|
|
113
|
+
} finally {
|
|
114
|
+
teeing = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
15
117
|
}
|
|
16
118
|
function scoped(scope) {
|
|
17
119
|
return {
|
|
@@ -246,8 +348,11 @@ function listFactory(d, ctx, byteOffset) {
|
|
|
246
348
|
writeAt,
|
|
247
349
|
commit: () => {
|
|
248
350
|
if (ctx.version) {
|
|
249
|
-
Atomics.add(ctx.version.view, ctx.version.index, 1);
|
|
351
|
+
const prev = Atomics.add(ctx.version.view, ctx.version.index, 1);
|
|
250
352
|
Atomics.notify(ctx.version.view, ctx.version.index);
|
|
353
|
+
if (ctx.path && devtoolsEnabled()) {
|
|
354
|
+
emitDevtools({ type: "memory:write", path: ctx.path, version: prev + 1 });
|
|
355
|
+
}
|
|
251
356
|
}
|
|
252
357
|
},
|
|
253
358
|
read: () => Array.from({ length: count }, (_, i) => readAt(i)),
|
|
@@ -349,6 +454,177 @@ var WorkerCrashedError = class extends Error {
|
|
|
349
454
|
}
|
|
350
455
|
};
|
|
351
456
|
|
|
457
|
+
// ../../src/userTiming.ts
|
|
458
|
+
var measureAtoll = (name, start, end, t) => {
|
|
459
|
+
const perf = globalThis.performance;
|
|
460
|
+
if (typeof perf?.measure !== "function") return;
|
|
461
|
+
try {
|
|
462
|
+
perf.measure(name, {
|
|
463
|
+
start,
|
|
464
|
+
end: Math.max(end, start),
|
|
465
|
+
detail: {
|
|
466
|
+
devtools: {
|
|
467
|
+
dataType: "track-entry",
|
|
468
|
+
track: t.track,
|
|
469
|
+
trackGroup: "atoll",
|
|
470
|
+
color: t.color ?? "primary",
|
|
471
|
+
properties: t.properties,
|
|
472
|
+
tooltipText: t.tooltipText
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
perf.clearMeasures?.(name);
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
var measureTaskCall = (runnerId, taskId, callId, slot, start, end, ok) => measureAtoll(`atoll task ${taskId}`, start, end, {
|
|
481
|
+
track: runnerId,
|
|
482
|
+
color: ok ? "primary" : "error",
|
|
483
|
+
properties: [["runner", runnerId], ["slot", slot], ["call", callId], ["outcome", ok ? "ok" : "error"]],
|
|
484
|
+
tooltipText: `${taskId} on ${runnerId}#${slot}: ${(end - start).toFixed(1)}ms`
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// ../../src/pool/workerChannel.ts
|
|
488
|
+
function setupChannelMemory(config, owner) {
|
|
489
|
+
if (config.sharedMemory || config.sharedBuffer !== void 0) {
|
|
490
|
+
if (typeof SharedArrayBuffer === "undefined" || typeof crossOriginIsolated !== "undefined" && !crossOriginIsolated) {
|
|
491
|
+
throw new Error(
|
|
492
|
+
`${owner} requires SharedArrayBuffer in a cross-origin isolated context \u2014 serve the page with COOP/COEP headers.`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
let injectedBuffer;
|
|
497
|
+
if (config.sharedBuffer !== void 0) {
|
|
498
|
+
injectedBuffer = typeof config.sharedBuffer === "function" ? config.sharedBuffer() : config.sharedBuffer;
|
|
499
|
+
if (!injectedBuffer) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`${owner}: \`sharedBuffer\` resolved to an empty buffer \u2014 the producing pool or contract has no bound memory yet.`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (!config.sharedMemory) return { sharedMemory: void 0, injectedBuffer, persistence: void 0 };
|
|
506
|
+
const sharedMemory = config.sharedMemory;
|
|
507
|
+
let memoryManager;
|
|
508
|
+
if (injectedBuffer) {
|
|
509
|
+
sharedMemory.bind(injectedBuffer);
|
|
510
|
+
} else {
|
|
511
|
+
memoryManager = new MemoryManager(config.memory);
|
|
512
|
+
memoryManager.ensureCapacity(sharedMemory.totalBytes);
|
|
513
|
+
sharedMemory.bind(memoryManager.getBuffer());
|
|
514
|
+
}
|
|
515
|
+
return { sharedMemory, memoryManager, injectedBuffer, persistence: config.persistence?.(sharedMemory) };
|
|
516
|
+
}
|
|
517
|
+
function sendInit(worker, mem) {
|
|
518
|
+
if (mem.memoryManager) {
|
|
519
|
+
worker.postMessage({ type: "INIT_MEMORY", memory: mem.memoryManager.memory, devtools: devtoolsEnabled() });
|
|
520
|
+
return "INIT_MEMORY";
|
|
521
|
+
}
|
|
522
|
+
if (mem.injectedBuffer) {
|
|
523
|
+
worker.postMessage({ type: "INIT_MEMORY", buffer: mem.injectedBuffer, devtools: devtoolsEnabled() });
|
|
524
|
+
return "INIT_MEMORY";
|
|
525
|
+
}
|
|
526
|
+
worker.postMessage({ type: "INIT", devtools: devtoolsEnabled() });
|
|
527
|
+
return "INIT";
|
|
528
|
+
}
|
|
529
|
+
function forwardWorkerDevtools(runnerId, slot, ev) {
|
|
530
|
+
const qualify = (id) => `${runnerId}#${slot}~${id}`;
|
|
531
|
+
forwardDevtools({
|
|
532
|
+
...ev,
|
|
533
|
+
...ev.poolId !== void 0 ? { poolId: qualify(ev.poolId) } : {},
|
|
534
|
+
worker: ev.worker === void 0 ? { poolId: runnerId, slot } : { poolId: qualify(ev.worker.poolId), slot: ev.worker.slot }
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ../../src/pool/devtoolsCommands.ts
|
|
539
|
+
var CHAOS_FAILURE = "chaos: injected failure";
|
|
540
|
+
var KILL_MESSAGE = "killed from devtools";
|
|
541
|
+
var runners = /* @__PURE__ */ new Map();
|
|
542
|
+
var uninstall = null;
|
|
543
|
+
var runnerOf = (args) => {
|
|
544
|
+
const id = args.poolId;
|
|
545
|
+
if (typeof id !== "string") throw new Error("poolId (string) is required");
|
|
546
|
+
const r = runners.get(id);
|
|
547
|
+
if (!r) {
|
|
548
|
+
throw new Error(
|
|
549
|
+
id.includes("~") ? `runner '${id}' lives inside a worker: nested pools are not controllable from the dashboard` : `no live runner '${id}' on this thread`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
return r;
|
|
553
|
+
};
|
|
554
|
+
var rate = (v, name) => {
|
|
555
|
+
if (v === void 0 || v === null) return void 0;
|
|
556
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 1) {
|
|
557
|
+
throw new Error(`${name} must be a number in 0..1`);
|
|
558
|
+
}
|
|
559
|
+
return v || void 0;
|
|
560
|
+
};
|
|
561
|
+
var parseChaos = (args) => {
|
|
562
|
+
const { delayMs } = args;
|
|
563
|
+
if (delayMs !== void 0 && delayMs !== null && (typeof delayMs !== "number" || !Number.isFinite(delayMs) || delayMs < 0)) {
|
|
564
|
+
throw new Error("delayMs must be a non-negative number");
|
|
565
|
+
}
|
|
566
|
+
const failRate = rate(args.failRate, "failRate");
|
|
567
|
+
const timeoutRate = rate(args.timeoutRate, "timeoutRate");
|
|
568
|
+
if ((failRate ?? 0) + (timeoutRate ?? 0) > 1) throw new Error("failRate + timeoutRate must not exceed 1");
|
|
569
|
+
const chaos = {};
|
|
570
|
+
if (delayMs) chaos.delayMs = delayMs;
|
|
571
|
+
if (failRate) chaos.failRate = failRate;
|
|
572
|
+
if (timeoutRate) chaos.timeoutRate = timeoutRate;
|
|
573
|
+
return Object.keys(chaos).length ? chaos : null;
|
|
574
|
+
};
|
|
575
|
+
var listRunners = () => [...runners.values()].map((r) => ({
|
|
576
|
+
poolId: r.poolId,
|
|
577
|
+
label: r.label ?? r.poolId,
|
|
578
|
+
size: r.size(),
|
|
579
|
+
dedicated: r.dedicated,
|
|
580
|
+
chaos: r.getChaos()
|
|
581
|
+
}));
|
|
582
|
+
var install = () => {
|
|
583
|
+
const offs = [
|
|
584
|
+
registerDevtoolsCommand("pool.list", () => listRunners()),
|
|
585
|
+
registerDevtoolsCommand("pool.stats", (args) => runnerOf(args).stats()),
|
|
586
|
+
registerDevtoolsCommand("worker.kill", (args) => {
|
|
587
|
+
const r = runnerOf(args);
|
|
588
|
+
const slot = args.slot ?? 0;
|
|
589
|
+
if (typeof slot !== "number" || !Number.isInteger(slot) || slot < 0) {
|
|
590
|
+
throw new Error("slot must be a non-negative integer");
|
|
591
|
+
}
|
|
592
|
+
r.kill(slot);
|
|
593
|
+
return { poolId: r.poolId, slot, killed: true };
|
|
594
|
+
}),
|
|
595
|
+
registerDevtoolsCommand("pool.chaos", (args) => {
|
|
596
|
+
const r = runnerOf(args);
|
|
597
|
+
r.setChaos(parseChaos(args));
|
|
598
|
+
return r.getChaos();
|
|
599
|
+
})
|
|
600
|
+
];
|
|
601
|
+
return () => offs.forEach((off) => off());
|
|
602
|
+
};
|
|
603
|
+
var registerDevtoolsRunner = (handle) => {
|
|
604
|
+
runners.set(handle.poolId, handle);
|
|
605
|
+
uninstall ??= install();
|
|
606
|
+
return () => {
|
|
607
|
+
if (runners.get(handle.poolId) !== handle) return;
|
|
608
|
+
runners.delete(handle.poolId);
|
|
609
|
+
if (runners.size === 0 && uninstall) {
|
|
610
|
+
uninstall();
|
|
611
|
+
uninstall = null;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
};
|
|
615
|
+
var applyChaos = (chaos, call) => {
|
|
616
|
+
const r = Math.random();
|
|
617
|
+
const fail = r < (chaos.failRate ?? 0);
|
|
618
|
+
const timeout = !fail && r < (chaos.failRate ?? 0) + (chaos.timeoutRate ?? 0);
|
|
619
|
+
if (!chaos.delayMs && !fail && !timeout) return call.post();
|
|
620
|
+
setTimeout(() => {
|
|
621
|
+
if (!call.live()) return;
|
|
622
|
+
if (fail) return call.fail();
|
|
623
|
+
if (timeout) call.timeout();
|
|
624
|
+
call.post();
|
|
625
|
+
}, chaos.delayMs ?? 0);
|
|
626
|
+
};
|
|
627
|
+
|
|
352
628
|
// ../../src/pool/workerPool.ts
|
|
353
629
|
var poolLog = scoped("pool");
|
|
354
630
|
var taskLog = scoped("task");
|
|
@@ -367,7 +643,10 @@ var WorkerPoolImpl = class {
|
|
|
367
643
|
pending = /* @__PURE__ */ new Map();
|
|
368
644
|
nextMessageId = 0;
|
|
369
645
|
queue = [];
|
|
646
|
+
mem;
|
|
370
647
|
memoryManager;
|
|
648
|
+
/** A caller-supplied buffer shipped via INIT_MEMORY (sub-worker / second-pool sharing). */
|
|
649
|
+
injectedBuffer;
|
|
371
650
|
workerUrl;
|
|
372
651
|
spawn;
|
|
373
652
|
concurrency;
|
|
@@ -375,6 +654,9 @@ var WorkerPoolImpl = class {
|
|
|
375
654
|
respawn;
|
|
376
655
|
taskTimeout;
|
|
377
656
|
closed = false;
|
|
657
|
+
/** Dashboard fault injection (`pool.chaos`); null unless devtools set it. */
|
|
658
|
+
chaos = null;
|
|
659
|
+
devtoolsOff;
|
|
378
660
|
drainWaiters = [];
|
|
379
661
|
statsAgg = {
|
|
380
662
|
completed: 0,
|
|
@@ -384,17 +666,18 @@ var WorkerPoolImpl = class {
|
|
|
384
666
|
waitMs: agg(),
|
|
385
667
|
runMs: agg()
|
|
386
668
|
};
|
|
669
|
+
/**
|
|
670
|
+
* Devtools correlation id, stamped on every event this pool emits:
|
|
671
|
+
* '<name>-p' when the config has a `name`, else 'pool-N'.
|
|
672
|
+
*/
|
|
673
|
+
poolId;
|
|
674
|
+
label;
|
|
675
|
+
nextCallId = 0;
|
|
387
676
|
sharedMemory;
|
|
388
677
|
/** The attached memory-persistence adapter, if the config supplied one. */
|
|
389
678
|
persistence;
|
|
390
679
|
constructor(config) {
|
|
391
|
-
|
|
392
|
-
if (typeof SharedArrayBuffer === "undefined" || typeof crossOriginIsolated !== "undefined" && !crossOriginIsolated) {
|
|
393
|
-
throw new Error(
|
|
394
|
-
"WorkerPool requires SharedArrayBuffer in a cross-origin isolated context \u2014 serve the page with COOP/COEP headers."
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
680
|
+
const mem = setupChannelMemory(config, "WorkerPool");
|
|
398
681
|
if (!config.workerUrl && !config.createWorker) {
|
|
399
682
|
throw new Error("WorkerPool requires either `workerUrl` or `createWorker` in its config.");
|
|
400
683
|
}
|
|
@@ -404,23 +687,49 @@ var WorkerPoolImpl = class {
|
|
|
404
687
|
this.maxQueue = config.maxQueue ?? Infinity;
|
|
405
688
|
this.respawn = config.respawn ?? true;
|
|
406
689
|
this.taskTimeout = config.taskTimeout;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
690
|
+
this.label = config.name;
|
|
691
|
+
this.poolId = nextDevtoolsId("pool", config.name);
|
|
692
|
+
this.mem = mem;
|
|
693
|
+
this.sharedMemory = mem.sharedMemory;
|
|
694
|
+
this.memoryManager = mem.memoryManager;
|
|
695
|
+
this.injectedBuffer = mem.injectedBuffer;
|
|
696
|
+
this.persistence = mem.persistence;
|
|
414
697
|
const requestedSize = config.poolSize ?? "auto";
|
|
415
|
-
const poolSize = requestedSize === "auto" ? navigator.hardwareConcurrency ?? 4 : requestedSize;
|
|
698
|
+
const poolSize = requestedSize === "auto" ? typeof navigator !== "undefined" ? navigator.hardwareConcurrency ?? 4 : 4 : requestedSize;
|
|
416
699
|
poolLog.info(
|
|
417
|
-
this.sharedMemory ? `spawning ${poolSize} worker(s) with ${fmtBytes(this.sharedMemory.totalBytes)} shared buffer` : `spawning ${poolSize} worker(s), message-only`,
|
|
700
|
+
this.sharedMemory ? `spawning ${poolSize} worker(s) with ${fmtBytes(this.sharedMemory.totalBytes)} shared buffer` : this.injectedBuffer ? `spawning ${poolSize} worker(s) with ${fmtBytes(this.injectedBuffer.byteLength)} inherited buffer` : `spawning ${poolSize} worker(s), message-only`,
|
|
418
701
|
{ workerUrl: config.workerUrl ? String(config.workerUrl) : "createWorker()" }
|
|
419
702
|
);
|
|
420
703
|
poolLog.debug(`tip: setLogLevel('trace') for per-task detail`);
|
|
704
|
+
emitDevtools({
|
|
705
|
+
type: "pool:init",
|
|
706
|
+
poolId: this.poolId,
|
|
707
|
+
label: this.label,
|
|
708
|
+
poolSize,
|
|
709
|
+
concurrency: this.concurrency,
|
|
710
|
+
memoryBytes: this.sharedMemory?.totalBytes ?? this.injectedBuffer?.byteLength
|
|
711
|
+
});
|
|
421
712
|
for (let i = 0; i < poolSize; i++) {
|
|
422
713
|
this.addWorker(this.spawn());
|
|
423
714
|
}
|
|
715
|
+
if (devtoolsEnabled()) {
|
|
716
|
+
this.devtoolsOff = registerDevtoolsRunner({
|
|
717
|
+
poolId: this.poolId,
|
|
718
|
+
label: this.label,
|
|
719
|
+
dedicated: false,
|
|
720
|
+
size: () => this.slots.length,
|
|
721
|
+
stats: () => this.stats(),
|
|
722
|
+
kill: (slot) => {
|
|
723
|
+
const s = this.slots[slot];
|
|
724
|
+
if (!s) throw new Error(`${this.poolId} has no worker slot ${slot}`);
|
|
725
|
+
this.handleWorkerError(s, { message: KILL_MESSAGE });
|
|
726
|
+
},
|
|
727
|
+
getChaos: () => this.chaos,
|
|
728
|
+
setChaos: (chaos) => {
|
|
729
|
+
this.chaos = chaos;
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
}
|
|
424
733
|
const tasks = config.tasks ?? {};
|
|
425
734
|
for (const [name, contract] of Object.entries(tasks)) {
|
|
426
735
|
if (name in this) {
|
|
@@ -437,18 +746,20 @@ var WorkerPoolImpl = class {
|
|
|
437
746
|
addWorker(worker) {
|
|
438
747
|
const slot = { worker, inFlight: 0 };
|
|
439
748
|
worker.addEventListener("message", (event) => {
|
|
440
|
-
|
|
749
|
+
const data = event.data;
|
|
750
|
+
if (data.type === "ATOLL_DEVTOOLS" && data.event) {
|
|
751
|
+
forwardWorkerDevtools(this.poolId, this.slots.indexOf(slot), data.event);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
this.handleReply(slot, data);
|
|
441
755
|
});
|
|
442
756
|
worker.addEventListener("error", (event) => {
|
|
443
757
|
this.handleWorkerError(slot, event);
|
|
444
758
|
});
|
|
445
|
-
|
|
446
|
-
worker.postMessage({ type: "INIT_MEMORY", memory: this.memoryManager.memory });
|
|
447
|
-
} else {
|
|
448
|
-
worker.postMessage({ type: "INIT" });
|
|
449
|
-
}
|
|
759
|
+
const init = sendInit(worker, this.mem);
|
|
450
760
|
this.slots.push(slot);
|
|
451
|
-
poolLog.debug(`worker spawned, ${
|
|
761
|
+
poolLog.debug(`worker spawned, ${init} sent`);
|
|
762
|
+
emitDevtools({ type: "worker:spawn", poolId: this.poolId, slot: this.slots.indexOf(slot) });
|
|
452
763
|
return slot;
|
|
453
764
|
}
|
|
454
765
|
handleReply(slot, data) {
|
|
@@ -459,14 +770,24 @@ var WorkerPoolImpl = class {
|
|
|
459
770
|
this.detach(entry);
|
|
460
771
|
if (!entry.done) {
|
|
461
772
|
entry.done = true;
|
|
462
|
-
|
|
773
|
+
const start = entry.sentAt ?? entry.enqueuedAt;
|
|
774
|
+
const runMs = performance.now() - start;
|
|
775
|
+
this.statsAgg.runMs.add(runMs);
|
|
463
776
|
if (data.success) {
|
|
464
777
|
this.statsAgg.completed++;
|
|
465
778
|
taskLog.debug(`\u2190 ${entry.contract.taskId} ok`);
|
|
779
|
+
if (devtoolsEnabled()) {
|
|
780
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "ok", runMs, resultBytes: estimateCloneBytes(data.result) });
|
|
781
|
+
measureTaskCall(this.poolId, entry.contract.taskId, entry.callId, this.slots.indexOf(slot), start, start + runMs, true);
|
|
782
|
+
}
|
|
466
783
|
entry.resolve(data.result);
|
|
467
784
|
} else {
|
|
468
785
|
this.statsAgg.failed++;
|
|
469
786
|
taskLog.warn(`\u2190 ${entry.contract.taskId} failed: ${data.error}`);
|
|
787
|
+
if (devtoolsEnabled()) {
|
|
788
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "error", runMs, error: data.error });
|
|
789
|
+
measureTaskCall(this.poolId, entry.contract.taskId, entry.callId, this.slots.indexOf(slot), start, start + runMs, false);
|
|
790
|
+
}
|
|
470
791
|
entry.reject(new Error(data.error));
|
|
471
792
|
}
|
|
472
793
|
}
|
|
@@ -478,6 +799,8 @@ var WorkerPoolImpl = class {
|
|
|
478
799
|
const e = event;
|
|
479
800
|
const message = e?.message ?? (e?.error ? String(e.error) : "worker error");
|
|
480
801
|
poolLog.warn(`worker error: ${message}`);
|
|
802
|
+
const idx = this.slots.indexOf(slot);
|
|
803
|
+
emitDevtools({ type: "worker:error", poolId: this.poolId, slot: idx, message });
|
|
481
804
|
for (const [id, entry] of [...this.pending]) {
|
|
482
805
|
if (entry.slot === slot) {
|
|
483
806
|
this.pending.delete(id);
|
|
@@ -485,20 +808,22 @@ var WorkerPoolImpl = class {
|
|
|
485
808
|
if (entry.done) continue;
|
|
486
809
|
entry.done = true;
|
|
487
810
|
this.statsAgg.failed++;
|
|
811
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: message });
|
|
488
812
|
entry.reject(new WorkerCrashedError(message));
|
|
489
813
|
}
|
|
490
814
|
}
|
|
491
|
-
const idx = this.slots.indexOf(slot);
|
|
492
815
|
if (idx >= 0) this.slots.splice(idx, 1);
|
|
493
816
|
slot.worker.terminate();
|
|
494
817
|
if (this.respawn) {
|
|
495
818
|
poolLog.info("respawning crashed worker");
|
|
496
819
|
this.addWorker(this.spawn());
|
|
820
|
+
emitDevtools({ type: "worker:respawn", poolId: this.poolId, slot: this.slots.length - 1 });
|
|
497
821
|
} else if (this.slots.length === 0) {
|
|
498
822
|
for (const entry of this.queue.splice(0)) {
|
|
499
823
|
entry.done = true;
|
|
500
824
|
this.detach(entry);
|
|
501
825
|
this.statsAgg.failed++;
|
|
826
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: message });
|
|
502
827
|
entry.reject(new WorkerCrashedError(message));
|
|
503
828
|
}
|
|
504
829
|
}
|
|
@@ -527,16 +852,26 @@ var WorkerPoolImpl = class {
|
|
|
527
852
|
entry.messageId = messageId;
|
|
528
853
|
entry.slot = slot;
|
|
529
854
|
entry.sentAt = performance.now();
|
|
530
|
-
|
|
855
|
+
const waitMs = entry.sentAt - entry.enqueuedAt;
|
|
856
|
+
this.statsAgg.waitMs.add(waitMs);
|
|
531
857
|
slot.inFlight++;
|
|
532
858
|
this.pending.set(messageId, entry);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
859
|
+
const slotIndex = this.slots.indexOf(slot);
|
|
860
|
+
taskLog.debug(`\u2192 ${entry.contract.taskId}`, { worker: slotIndex, args: entry.args });
|
|
861
|
+
if (devtoolsEnabled()) {
|
|
862
|
+
emitDevtools({ type: "task:dispatch", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, slot: slotIndex, waitMs, argBytes: estimateCloneBytes(entry.args) });
|
|
863
|
+
}
|
|
864
|
+
const msg = { type: "EXECUTE_TASK", messageId, taskId: entry.contract.taskId, args: entry.args };
|
|
865
|
+
if (this.chaos) {
|
|
866
|
+
applyChaos(this.chaos, {
|
|
867
|
+
live: () => this.pending.get(messageId) === entry,
|
|
868
|
+
fail: () => this.handleReply(slot, { messageId, taskId: msg.taskId, success: false, error: CHAOS_FAILURE }),
|
|
869
|
+
timeout: () => this.cancel(entry, new TaskTimeoutError()),
|
|
870
|
+
post: () => slot.worker.postMessage(msg)
|
|
871
|
+
});
|
|
872
|
+
} else {
|
|
873
|
+
slot.worker.postMessage(msg);
|
|
874
|
+
}
|
|
540
875
|
}
|
|
541
876
|
/** Detaches signal/timeout listeners from a finalized entry. */
|
|
542
877
|
detach(entry) {
|
|
@@ -557,6 +892,7 @@ var WorkerPoolImpl = class {
|
|
|
557
892
|
entry.done = true;
|
|
558
893
|
this.detach(entry);
|
|
559
894
|
this.statsAgg.aborted++;
|
|
895
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: err instanceof TaskTimeoutError ? "timeout" : "aborted" });
|
|
560
896
|
entry.reject(err);
|
|
561
897
|
this.drainQueue();
|
|
562
898
|
this.checkDrained();
|
|
@@ -582,6 +918,7 @@ var WorkerPoolImpl = class {
|
|
|
582
918
|
const entry = {
|
|
583
919
|
contract,
|
|
584
920
|
args,
|
|
921
|
+
callId: ++this.nextCallId,
|
|
585
922
|
enqueuedAt: performance.now(),
|
|
586
923
|
resolve,
|
|
587
924
|
reject,
|
|
@@ -596,12 +933,14 @@ var WorkerPoolImpl = class {
|
|
|
596
933
|
if (timeout !== void 0) {
|
|
597
934
|
entry.timer = setTimeout(() => this.cancel(entry, new TaskTimeoutError()), timeout);
|
|
598
935
|
}
|
|
936
|
+
emitDevtools({ type: "task:enqueue", poolId: this.poolId, callId: entry.callId, taskId: contract.taskId });
|
|
599
937
|
const slot = this.pickWorker();
|
|
600
938
|
if (slot) {
|
|
601
939
|
this.send(slot, entry);
|
|
602
940
|
} else if (this.queue.length >= this.maxQueue) {
|
|
603
941
|
entry.done = true;
|
|
604
942
|
this.detach(entry);
|
|
943
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: contract.taskId, outcome: "queue-full" });
|
|
605
944
|
reject(new PoolQueueFullError());
|
|
606
945
|
return;
|
|
607
946
|
} else {
|
|
@@ -622,7 +961,7 @@ var WorkerPoolImpl = class {
|
|
|
622
961
|
* (e.g. a second pool of HTTP workers via withSharedBuffer).
|
|
623
962
|
*/
|
|
624
963
|
get sharedBuffer() {
|
|
625
|
-
return this.memoryManager?.getBuffer();
|
|
964
|
+
return this.memoryManager?.getBuffer() ?? this.injectedBuffer;
|
|
626
965
|
}
|
|
627
966
|
/**
|
|
628
967
|
* Live snapshot of the pool's worker slots — for auxiliary messaging that
|
|
@@ -662,16 +1001,19 @@ var WorkerPoolImpl = class {
|
|
|
662
1001
|
}
|
|
663
1002
|
terminate() {
|
|
664
1003
|
this.closed = true;
|
|
1004
|
+
this.devtoolsOff?.();
|
|
665
1005
|
void this.persistence?.stop()?.catch?.(
|
|
666
1006
|
(e) => poolLog.warn("memory persistence stop failed", e)
|
|
667
1007
|
);
|
|
668
1008
|
poolLog.info(`terminating ${this.slots.length} worker(s)`);
|
|
1009
|
+
emitDevtools({ type: "pool:terminate", poolId: this.poolId });
|
|
669
1010
|
const crashed = new WorkerCrashedError("pool terminated");
|
|
670
1011
|
for (const [, entry] of this.pending) {
|
|
671
1012
|
this.detach(entry);
|
|
672
1013
|
if (entry.done) continue;
|
|
673
1014
|
entry.done = true;
|
|
674
1015
|
this.statsAgg.failed++;
|
|
1016
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: "pool terminated" });
|
|
675
1017
|
entry.reject(crashed);
|
|
676
1018
|
}
|
|
677
1019
|
this.pending.clear();
|
|
@@ -679,6 +1021,7 @@ var WorkerPoolImpl = class {
|
|
|
679
1021
|
entry.done = true;
|
|
680
1022
|
this.detach(entry);
|
|
681
1023
|
this.statsAgg.failed++;
|
|
1024
|
+
emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "crashed", error: "pool terminated" });
|
|
682
1025
|
entry.reject(crashed);
|
|
683
1026
|
}
|
|
684
1027
|
for (const slot of this.slots) {
|
package/dist/index.js
CHANGED
package/dist/redis.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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.
|
|
37
|
+
"@atolljs/core": "0.1.8"
|
|
38
38
|
},
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|