Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions defaultmodules/updatenotification/update_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/functions/update_helper_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});