c8ctl-plugin-nano 1.26.2 → 1.26.3
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/c8ctl-plugin.js +83 -3
- package/package.json +8 -8
package/c8ctl-plugin.js
CHANGED
|
@@ -3902,6 +3902,12 @@ const SUPERVISOR_PROBE_RESPONSE_TIMEOUT_MS = 2_000;
|
|
|
3902
3902
|
// Hard cap on a single connection's inbound buffer, so a misbehaving client
|
|
3903
3903
|
// can't grow the daemon's memory without bound with a newline-free frame.
|
|
3904
3904
|
const SUPERVISOR_MAX_FRAME_BYTES = 1 << 20; // 1 MiB
|
|
3905
|
+
// How often the daemon re-samples worker activity to push a refreshed status to
|
|
3906
|
+
// attached consoles. The push is change-gated (see supervisorStatusSignature),
|
|
3907
|
+
// so a quiet fleet stays silent; only real transitions (idle↔busy, a new job,
|
|
3908
|
+
// restart/exit) reprint the table. `NANO_SUPERVISOR_MONITOR_MS=0` disables the
|
|
3909
|
+
// live refresh (falling back to the attach-time snapshot + lifecycle events).
|
|
3910
|
+
const SUPERVISOR_MONITOR_INTERVAL_MS = 1_000;
|
|
3905
3911
|
|
|
3906
3912
|
// The `nano work` flags forwarded verbatim to each spawned child.
|
|
3907
3913
|
// kind: 'value' → `--flag v`; 'boolean' → `--flag`; 'list' → repeated `--flag v`.
|
|
@@ -4184,6 +4190,33 @@ function summarizeSupervisorWorker(w, now = Date.now()) {
|
|
|
4184
4190
|
};
|
|
4185
4191
|
}
|
|
4186
4192
|
|
|
4193
|
+
/**
|
|
4194
|
+
* A stable fingerprint of the fleet's *observable* state for change detection.
|
|
4195
|
+
* Deliberately excludes ticking durations (uptimeMs, per-job sinceMs) so that a
|
|
4196
|
+
* merely-elapsing clock doesn't count as a change — only real transitions (a
|
|
4197
|
+
* worker going up/down, idle↔busy, picking up/finishing a job, a restart) alter
|
|
4198
|
+
* the signature. The daemon uses this to push a refreshed status to attached
|
|
4199
|
+
* consoles only when something actually changed, keeping a quiet fleet silent.
|
|
4200
|
+
* `workers` is an array of `summarizeSupervisorWorker` results.
|
|
4201
|
+
*/
|
|
4202
|
+
function supervisorStatusSignature(workers) {
|
|
4203
|
+
const list = Array.isArray(workers) ? workers : [];
|
|
4204
|
+
return JSON.stringify(
|
|
4205
|
+
list.map((w) => [
|
|
4206
|
+
w.id,
|
|
4207
|
+
w.profile ?? '',
|
|
4208
|
+
w.state,
|
|
4209
|
+
w.pid ?? 0,
|
|
4210
|
+
Number(w.restarts) || 0,
|
|
4211
|
+
w.lastExit ?? '',
|
|
4212
|
+
w.activity ? w.activity.state : null,
|
|
4213
|
+
w.activity
|
|
4214
|
+
? w.activity.jobs.map((j) => `${j.key}\u0000${j.type ?? ''}`).sort()
|
|
4215
|
+
: null,
|
|
4216
|
+
]),
|
|
4217
|
+
);
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4187
4220
|
/** One-line JOB cell for a status row: the serviced job key, `idle`, or `-`. */
|
|
4188
4221
|
function supervisorJobCell(w) {
|
|
4189
4222
|
if (w.state !== 'running') return '-';
|
|
@@ -4338,6 +4371,10 @@ async function runSupervisorDaemon() {
|
|
|
4338
4371
|
const workers = new Map();
|
|
4339
4372
|
const attachClients = new Set();
|
|
4340
4373
|
let shuttingDown = false;
|
|
4374
|
+
// Live-view monitor: tracks the last-broadcast fleet signature so we push a
|
|
4375
|
+
// refreshed status to attached consoles only on real change (see below).
|
|
4376
|
+
let monitorTimer = null;
|
|
4377
|
+
let lastMonitorSig = null;
|
|
4341
4378
|
|
|
4342
4379
|
// Daemon-wide mutation serialization: `add`/`remove`/`restart` must not
|
|
4343
4380
|
// interleave, or two clients racing the same worker could each spawn an
|
|
@@ -4501,11 +4538,15 @@ async function runSupervisorDaemon() {
|
|
|
4501
4538
|
return [...workers.values()].filter((w) => w.profile === t).map((w) => w.id);
|
|
4502
4539
|
};
|
|
4503
4540
|
|
|
4504
|
-
|
|
4541
|
+
// `pub` lets a caller that has already sampled the fleet (e.g. the monitor
|
|
4542
|
+
// tick, which needs the snapshot to compute its change signature) reuse that
|
|
4543
|
+
// exact snapshot for the frame — so the broadcast payload is guaranteed to
|
|
4544
|
+
// match the signature that decided to send it, with no second re-sample.
|
|
4545
|
+
const statusFrame = (final, pub) => ({
|
|
4505
4546
|
ok: true,
|
|
4506
4547
|
type: 'status',
|
|
4507
4548
|
daemon: { pid: process.pid, startedAt, socket: socketPath, logFile: daemonLogFile },
|
|
4508
|
-
workers: [...workers.values()].map(workerPublic),
|
|
4549
|
+
workers: pub || [...workers.values()].map(workerPublic),
|
|
4509
4550
|
...(final ? { final: true } : {}),
|
|
4510
4551
|
});
|
|
4511
4552
|
|
|
@@ -4515,6 +4556,7 @@ async function runSupervisorDaemon() {
|
|
|
4515
4556
|
// Let any in-flight mutation finish before we snapshot the worker set, so
|
|
4516
4557
|
// an add/restart racing the shutdown can't leave an orphaned child behind.
|
|
4517
4558
|
try { await opQueue; } catch { /* mutation already logged */ }
|
|
4559
|
+
if (monitorTimer) { try { clearInterval(monitorTimer); } catch { /* ignore */ } monitorTimer = null; }
|
|
4518
4560
|
dlog(`received ${signal || 'stop'} — stopping ${workers.size} worker(s)`);
|
|
4519
4561
|
await Promise.all([...workers.keys()].map((id) => stopWorker(id)));
|
|
4520
4562
|
broadcast({ type: 'event', event: 'daemon-stop' });
|
|
@@ -4655,6 +4697,37 @@ async function runSupervisorDaemon() {
|
|
|
4655
4697
|
dlog(`supervisor daemon up (pid ${process.pid}) — control ${socketPath}`);
|
|
4656
4698
|
persist();
|
|
4657
4699
|
|
|
4700
|
+
// Live-view refresh: periodically re-sample worker activity and push a fresh
|
|
4701
|
+
// status to attached consoles, but only when the fleet's observable state
|
|
4702
|
+
// actually changed since the last push (idle↔busy, a new/finished job, a
|
|
4703
|
+
// restart/exit). This keeps an attached `supervisor` console current without
|
|
4704
|
+
// spamming a quiet fleet. The signature always tracks the latest state (even
|
|
4705
|
+
// with no clients attached) so an idle-fleet attach — whose snapshot already
|
|
4706
|
+
// matches the tracked signature — won't provoke a redundant reprint for
|
|
4707
|
+
// everyone on the next tick. (A change that lands in the sub-tick window
|
|
4708
|
+
// *between* a tick and a fresh attach can still yield one extra identical
|
|
4709
|
+
// frame to the newcomer; that reprint is required to inform the already-
|
|
4710
|
+
// attached clients of the change, and is harmless — same content, re-rendered.)
|
|
4711
|
+
// Env-gated: NANO_SUPERVISOR_MONITOR_MS=0 disables; otherwise it's the cadence.
|
|
4712
|
+
const monitorMs = (() => {
|
|
4713
|
+
const raw = process.env.NANO_SUPERVISOR_MONITOR_MS;
|
|
4714
|
+
if (raw == null || raw === '') return SUPERVISOR_MONITOR_INTERVAL_MS;
|
|
4715
|
+
const n = Number(raw);
|
|
4716
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : SUPERVISOR_MONITOR_INTERVAL_MS;
|
|
4717
|
+
})();
|
|
4718
|
+
if (monitorMs > 0) {
|
|
4719
|
+
lastMonitorSig = supervisorStatusSignature([...workers.values()].map(workerPublic));
|
|
4720
|
+
monitorTimer = setInterval(() => {
|
|
4721
|
+
if (shuttingDown) return;
|
|
4722
|
+
const pub = [...workers.values()].map(workerPublic);
|
|
4723
|
+
const sig = supervisorStatusSignature(pub);
|
|
4724
|
+
const changed = sig !== lastMonitorSig;
|
|
4725
|
+
lastMonitorSig = sig;
|
|
4726
|
+
if (changed && attachClients.size > 0) broadcast(statusFrame(false, pub));
|
|
4727
|
+
}, monitorMs);
|
|
4728
|
+
if (typeof monitorTimer.unref === 'function') monitorTimer.unref();
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4658
4731
|
// Keep the event loop alive indefinitely; the server holds it, but add an
|
|
4659
4732
|
// explicit never-resolving guard so a transient server close can't exit us.
|
|
4660
4733
|
await new Promise(() => {});
|
|
@@ -4986,10 +5059,12 @@ async function attachSupervisorConsole(state) {
|
|
|
4986
5059
|
sock.write(encodeFrame({ op: 'attach' }));
|
|
4987
5060
|
|
|
4988
5061
|
let buf = '';
|
|
5062
|
+
let rl = null;
|
|
4989
5063
|
sock.on('data', (chunk) => {
|
|
4990
5064
|
buf += chunk;
|
|
4991
5065
|
const { frames, rest } = decodeFrames(buf);
|
|
4992
5066
|
buf = rest;
|
|
5067
|
+
if (frames.length === 0) return;
|
|
4993
5068
|
for (const frame of frames) {
|
|
4994
5069
|
if (frame.type === 'status') {
|
|
4995
5070
|
out('');
|
|
@@ -5010,9 +5085,13 @@ async function attachSupervisorConsole(state) {
|
|
|
5010
5085
|
out(`! ${frame.error}`);
|
|
5011
5086
|
}
|
|
5012
5087
|
}
|
|
5088
|
+
// A pushed frame writes straight to stdout, stepping on the readline prompt
|
|
5089
|
+
// and any half-typed command. Re-render the prompt (preserving the input
|
|
5090
|
+
// buffer) so an async live-view refresh doesn't corrupt what the user typed.
|
|
5091
|
+
if (rl) { try { rl.prompt(true); } catch { /* ignore */ } }
|
|
5013
5092
|
});
|
|
5014
5093
|
|
|
5015
|
-
|
|
5094
|
+
rl = createReadline({ input: process.stdin, output: process.stdout, prompt: 'supervisor> ' });
|
|
5016
5095
|
rl.prompt();
|
|
5017
5096
|
|
|
5018
5097
|
await new Promise((resolve) => {
|
|
@@ -6519,6 +6598,7 @@ export {
|
|
|
6519
6598
|
formatDuration,
|
|
6520
6599
|
summarizeSupervisorWorker,
|
|
6521
6600
|
formatSupervisorStatus,
|
|
6601
|
+
supervisorStatusSignature,
|
|
6522
6602
|
supervisorJobCell,
|
|
6523
6603
|
supervisorWorkerActivityFile,
|
|
6524
6604
|
WORK_FORWARD_FLAGS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "c8ctl plugin to start, inspect, and stop a local Nano BPM (nanobpmn) cluster",
|
|
6
6
|
"main": "c8ctl-plugin.js",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"semantic-release": "^25.0.3"
|
|
48
48
|
},
|
|
49
49
|
"optionalDependencies": {
|
|
50
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.26.
|
|
51
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.26.
|
|
52
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.26.
|
|
53
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.26.
|
|
54
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.26.
|
|
55
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.26.
|
|
56
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.26.
|
|
50
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.26.3",
|
|
51
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.26.3",
|
|
52
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.26.3",
|
|
53
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.26.3",
|
|
54
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.26.3",
|
|
55
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.26.3",
|
|
56
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.26.3"
|
|
57
57
|
}
|
|
58
58
|
}
|