mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-06-04 10:19:29 +00:00
fix(updatenotification): don't spawn a child process when running under PM2 (#4166)
Previously, `nodeRestart()` would spawn a detached child and exit. Under PM2 that's a problem: PM2 also respawns on exit, so both race to bind the same port. The fix: When `process.env.pm_id` is set, just exit and let PM2 handle the restart. The spawn logic is moved into its own method so it can be tested cleanly. Partially fixes #4165
This commit is contained in:
committed by
GitHub
parent
e55b11be5d
commit
03e4eef3d1
@@ -130,21 +130,43 @@ class Updater {
|
||||
});
|
||||
}
|
||||
|
||||
// restart MagicMirror with the same start command as the current process
|
||||
/**
|
||||
* Restart the current MagicMirror process after a successful auto-update.
|
||||
* Under PM2 just exit and let PM2 respawn — spawning a child here would
|
||||
* race against PM2 and cause an EADDRINUSE conflict (see issue #4165).
|
||||
* Standalone: spawn a detached clone of the current process, then exit.
|
||||
*/
|
||||
nodeRestart () {
|
||||
Log.info("Restarting MagicMirror...");
|
||||
const out = process.stdout;
|
||||
const err = process.stderr;
|
||||
|
||||
// Restart with the same binary and arguments as the current process
|
||||
const binary = process.argv[0];
|
||||
const args = process.argv.slice(1);
|
||||
const options = { cwd: this.root_path, detached: true, stdio: ["ignore", out, err] };
|
||||
const subprocess = Spawn(binary, args, options);
|
||||
subprocess.unref(); // allow the current process to exit without waiting for the subprocess
|
||||
const isManagedByPm2 = process.env.pm_id !== undefined;
|
||||
if (isManagedByPm2) {
|
||||
Log.info("Running under PM2 — exiting for PM2 to respawn.");
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
this._spawnDetachedSelf();
|
||||
process.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a detached clone of the current Node process (same binary + argv),
|
||||
* wired to the parent's stdout/stderr.
|
||||
*/
|
||||
_spawnDetachedSelf () {
|
||||
const nodeBinary = process.argv[0];
|
||||
const nodeArgs = process.argv.slice(1);
|
||||
const spawnOptions = {
|
||||
cwd: this.root_path,
|
||||
detached: true,
|
||||
stdio: ["ignore", process.stdout, process.stderr]
|
||||
};
|
||||
|
||||
const child = Spawn(nodeBinary, nodeArgs, spawnOptions);
|
||||
child.unref();
|
||||
}
|
||||
|
||||
// check if module is MagicMirror
|
||||
isMagicMirror (module) {
|
||||
if (module === "MagicMirror") return true;
|
||||
|
||||
@@ -87,4 +87,33 @@ describe("UpdateHelper", () => {
|
||||
vi.advanceTimersByTime(3000);
|
||||
expect(nodeRestartSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("nodeRestart", () => {
|
||||
it("exits without spawning when running under PM2", async () => {
|
||||
process.env.pm_id = "0";
|
||||
|
||||
const updater = await createUpdater();
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {});
|
||||
const spawnSpy = vi.spyOn(updater, "_spawnDetachedSelf").mockImplementation(() => {});
|
||||
|
||||
updater.nodeRestart();
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
expect(spawnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("spawns a detached child process when not running under PM2", async () => {
|
||||
delete process.env.pm_id;
|
||||
|
||||
const updater = await createUpdater();
|
||||
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {});
|
||||
const spawnSpy = vi.spyOn(updater, "_spawnDetachedSelf").mockImplementation(() => {});
|
||||
|
||||
updater.nodeRestart();
|
||||
|
||||
expect(spawnSpy).toHaveBeenCalledOnce();
|
||||
expect(exitSpy).toHaveBeenCalledOnce();
|
||||
expect(exitSpy).not.toHaveBeenCalledWith(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user