quilltap 4.10.0-dev.5 → 4.10.0-dev.9
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/bin/quilltap.js +55 -48
- package/package.json +1 -1
package/bin/quilltap.js
CHANGED
|
@@ -484,6 +484,39 @@ function verifyPidIsNode(pid, expectedArgv0) {
|
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
+
const HEARTBEAT_FRESH_MS = 5 * 60 * 1000;
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Decide whether a lock record belongs to a live process.
|
|
491
|
+
*
|
|
492
|
+
* A hostname that differs from ours does NOT mean a different machine: macOS
|
|
493
|
+
* derives gethostname() dynamically when `scutil --get HostName` is unset, so
|
|
494
|
+
* one Mac reports e.g. "MacBook-Pro.local" and "Mac" at different times. So
|
|
495
|
+
* check PID liveness regardless of the recorded name, and fall back to
|
|
496
|
+
* heartbeat freshness for any environment rather than only for containers.
|
|
497
|
+
* See bug 126.
|
|
498
|
+
*/
|
|
499
|
+
function assessLock(lock, hostname) {
|
|
500
|
+
const sameHost = lock.hostname === hostname;
|
|
501
|
+
const alive = isPidAlive(lock.pid);
|
|
502
|
+
const isNode = alive ? verifyPidIsNode(lock.pid, lock.processArgv0 || '') : false;
|
|
503
|
+
const heartbeatAgeMs = lock.lastHeartbeat
|
|
504
|
+
? Date.now() - new Date(lock.lastHeartbeat).getTime()
|
|
505
|
+
: Infinity;
|
|
506
|
+
const heartbeatFresh = heartbeatAgeMs < HEARTBEAT_FRESH_MS;
|
|
507
|
+
|
|
508
|
+
return {
|
|
509
|
+
sameHost,
|
|
510
|
+
alive,
|
|
511
|
+
isNode,
|
|
512
|
+
heartbeatAgeMs,
|
|
513
|
+
heartbeatFresh,
|
|
514
|
+
// Held by something running: a confirmed local process, or any holder
|
|
515
|
+
// still refreshing its heartbeat.
|
|
516
|
+
live: (alive && isNode) || heartbeatFresh,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
487
520
|
/**
|
|
488
521
|
* Handle --lock-status, --lock-clean, --lock-override commands.
|
|
489
522
|
*/
|
|
@@ -518,32 +551,20 @@ function handleLockCommand(dataDir, opts) {
|
|
|
518
551
|
return;
|
|
519
552
|
}
|
|
520
553
|
|
|
521
|
-
const sameHost
|
|
522
|
-
|
|
523
|
-
const isNode = alive ? verifyPidIsNode(lock.pid, lock.processArgv0 || '') : false;
|
|
554
|
+
const { sameHost, alive, isNode, heartbeatAgeMs, heartbeatFresh } =
|
|
555
|
+
assessLock(lock, hostname);
|
|
524
556
|
|
|
525
557
|
// Status line
|
|
526
558
|
let status;
|
|
527
559
|
if (alive && isNode) {
|
|
528
560
|
status = '\x1b[32mACTIVE\x1b[0m (process confirmed running)';
|
|
561
|
+
} else if (heartbeatFresh) {
|
|
562
|
+
const ageStr = Math.round(heartbeatAgeMs / 1000) + 's';
|
|
563
|
+
status = `\x1b[32mACTIVE\x1b[0m (${lock.environment || 'unknown'}, heartbeat ${ageStr} ago)`;
|
|
529
564
|
} else if (alive && !isNode) {
|
|
530
565
|
status = '\x1b[33mSUSPECT\x1b[0m (PID alive but does not look like Quilltap — possible PID reuse)';
|
|
531
566
|
} else if (!sameHost) {
|
|
532
|
-
|
|
533
|
-
const isVMOrContainer = lock.environment === 'docker';
|
|
534
|
-
const heartbeatAgeMs = lock.lastHeartbeat
|
|
535
|
-
? Date.now() - new Date(lock.lastHeartbeat).getTime()
|
|
536
|
-
: Infinity;
|
|
537
|
-
const heartbeatFreshMs = 5 * 60 * 1000;
|
|
538
|
-
|
|
539
|
-
if (isVMOrContainer && heartbeatAgeMs < heartbeatFreshMs) {
|
|
540
|
-
const ageStr = Math.round(heartbeatAgeMs / 1000) + 's';
|
|
541
|
-
status = `\x1b[32mACTIVE (${lock.environment}, heartbeat ${ageStr} ago)\x1b[0m`;
|
|
542
|
-
} else if (isVMOrContainer) {
|
|
543
|
-
status = `\x1b[33mSTALE (${lock.environment}, no recent heartbeat)\x1b[0m — will be auto-claimed on next startup`;
|
|
544
|
-
} else {
|
|
545
|
-
status = '\x1b[33mSTALE (different host)\x1b[0m — will be auto-claimed on next startup';
|
|
546
|
-
}
|
|
567
|
+
status = `\x1b[33mSTALE (${lock.environment || 'unknown'} on ${lock.hostname}, no recent heartbeat)\x1b[0m — will be auto-claimed on next startup`;
|
|
547
568
|
} else {
|
|
548
569
|
status = '\x1b[31mSTALE (process dead)\x1b[0m — will be auto-claimed on next startup';
|
|
549
570
|
}
|
|
@@ -551,7 +572,7 @@ function handleLockCommand(dataDir, opts) {
|
|
|
551
572
|
console.log(`Instance Lock Status: ${status}`);
|
|
552
573
|
console.log();
|
|
553
574
|
console.log(` PID: ${lock.pid}`);
|
|
554
|
-
console.log(` Hostname: ${lock.hostname}${sameHost ? ' (this host)' :
|
|
575
|
+
console.log(` Hostname: ${lock.hostname}${sameHost ? ' (this host)' : ` (recorded name differs from ours: ${hostname})`}`);
|
|
555
576
|
console.log(` Environment: ${lock.environment || 'unknown'}`);
|
|
556
577
|
console.log(` Process: ${lock.processTitle || 'unknown'}`);
|
|
557
578
|
console.log(` Started: ${lock.startedAt || 'unknown'}`);
|
|
@@ -599,37 +620,23 @@ function handleLockCommand(dataDir, opts) {
|
|
|
599
620
|
return;
|
|
600
621
|
}
|
|
601
622
|
|
|
602
|
-
const sameHost
|
|
603
|
-
|
|
623
|
+
const { sameHost, alive, isNode, heartbeatAgeMs, heartbeatFresh } =
|
|
624
|
+
assessLock(lock, hostname);
|
|
604
625
|
|
|
605
|
-
if (alive) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
626
|
+
if (alive && isNode) {
|
|
627
|
+
console.log(`Lock is held by a live Quilltap process (PID ${lock.pid}). Cannot clean.`);
|
|
628
|
+
console.log('Stop the running instance first, or use --lock-override to force.');
|
|
629
|
+
process.exit(1);
|
|
630
|
+
} else if (heartbeatFresh) {
|
|
631
|
+
const ageStr = Math.round(heartbeatAgeMs / 1000) + 's';
|
|
632
|
+
console.log(`Lock is still being refreshed (heartbeat ${ageStr} ago) — its holder is alive. Cannot clean.`);
|
|
633
|
+
console.log('Stop the running instance first, or use --lock-override to force.');
|
|
634
|
+
process.exit(1);
|
|
635
|
+
} else if (alive && !isNode) {
|
|
636
|
+
console.log(`Lock references PID ${lock.pid} which is alive but does NOT look like a Quilltap process.`);
|
|
637
|
+
console.log('This is likely a stale lock with a reused PID. Removing.');
|
|
615
638
|
} else if (!sameHost) {
|
|
616
|
-
|
|
617
|
-
const isVMOrContainer = lock.environment === 'docker';
|
|
618
|
-
const heartbeatAgeMs = lock.lastHeartbeat
|
|
619
|
-
? Date.now() - new Date(lock.lastHeartbeat).getTime()
|
|
620
|
-
: Infinity;
|
|
621
|
-
const heartbeatFreshMs = 5 * 60 * 1000;
|
|
622
|
-
|
|
623
|
-
if (isVMOrContainer && heartbeatAgeMs < heartbeatFreshMs) {
|
|
624
|
-
const ageStr = Math.round(heartbeatAgeMs / 1000) + 's';
|
|
625
|
-
console.log(`Lock is held by a live ${lock.environment} instance (heartbeat ${ageStr} ago). Cannot clean.`);
|
|
626
|
-
console.log('Stop the other instance first, or use --lock-override to force.');
|
|
627
|
-
process.exit(1);
|
|
628
|
-
} else if (isVMOrContainer) {
|
|
629
|
-
console.log(`Lock was held by ${lock.environment} (${lock.hostname}) with no recent heartbeat. Removing stale lock.`);
|
|
630
|
-
} else {
|
|
631
|
-
console.log(`Lock was held by a different host (${lock.hostname}). Removing stale lock.`);
|
|
632
|
-
}
|
|
639
|
+
console.log(`Lock was held by ${lock.environment || 'unknown'} on ${lock.hostname} with no recent heartbeat. Removing stale lock.`);
|
|
633
640
|
} else {
|
|
634
641
|
console.log(`Lock was held by PID ${lock.pid} which is no longer running. Removing stale lock.`);
|
|
635
642
|
}
|