Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure the machine never sleeps...ever #690

Closed
wants to merge 1 commit into from
Closed
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
93 changes: 75 additions & 18 deletions packages/server/src/server/services/caffeinateService/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as process from "process";
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
import { Loggable } from "@server/lib/logging/Loggable";
import { setInterval } from "timers";

/**
* Service that spawns the caffeinate process so that
Expand All @@ -11,57 +12,113 @@ export class CaffeinateService extends Loggable {
tag = "CaffeinateService";

isCaffeinated = false;

childProc: ChildProcessWithoutNullStreams;
watchdogInterval: NodeJS.Timeout;

/**
* Runs caffeinate until the current process is killed
* Runs caffeinate with stricter settings and additional safeguards
* to ensure the system does not sleep until the current process is killed.
*/
start() {
if (this.isCaffeinated) return;
const myPid = process.pid;

// Spawn the child process
// -i: Create an assertion to prevent the system from idle sleeping.
// -m: Create an assertion to prevent the disk from idle sleeping.
// -s: Create an assertion to prevent the system from sleeping
// -w: Waits for the process with the specified pid to exit.
this.childProc = spawn("caffeinate", ["-i", "-m", "-s", "-w", myPid.toString()], { detached: true });
// Spawn the child process with stricter flags to prevent sleep under all conditions
// -i: Prevent idle sleep.
// -m: Prevent disk sleep.
// -s: Prevent system sleep.
// -d: Prevent display sleep.
// -u: Simulate user activity to prevent display sleep.
// -t 0: Run indefinitely.
// -w: Wait for the process with the specified pid to exit.
this.childProc = spawn("caffeinate", ["-i", "-m", "-s", "-d", "-u", "-t", "0", "-w", myPid.toString()], { detached: true });
this.log.info(`Spawned Caffeinate with PID: ${this.childProc.pid}`);
this.isCaffeinated = true;

// Setup listeners
this.childProc.on("close", this.onClose);
this.childProc.on("exit", this.onClose);
this.childProc.on("disconnect", this.onClose);
this.childProc.on("error", this.onClose);
// Setup listeners for enhanced error handling and recovery
this.childProc.on("close", (code) => this.onClose(code));
this.childProc.on("exit", (code) => this.onExit(code));
this.childProc.on("disconnect", () => this.onDisconnect());
this.childProc.on("error", (err) => this.onError(err));

// Watchdog timer to check if the process is still alive
this.watchdogInterval = setInterval(() => {
if (this.childProc.killed || !this.childProc.connected) {
this.log.warn("Caffeinate process appears to have stopped. Restarting...");
this.start(); // Restart caffeinate if it has stopped
}
}, 5000); // Check every 5 seconds

// Ensure the process is not prematurely terminated
process.on('SIGTERM', () => this.stop());
process.on('SIGINT', () => this.stop());
process.on('uncaughtException', (err) => {
this.log.error(`Uncaught exception: ${err.message}`);
this.stop(); // Stop caffeinate if an uncaught exception occurs
process.exit(1);
});
}

/**
* Stops being caffeinated
* Stops the caffeinate process safely
*/
stop() {
if (!this.isCaffeinated) return;
clearInterval(this.watchdogInterval); // Stop the watchdog

if (this.childProc && this.childProc.pid) {
// Kill the process
try {
const killed = this.childProc.kill();
if (!killed) process.kill(-this.childProc.pid);
if (!killed) process.kill(-this.childProc.pid); // Force kill if needed
this.log.debug("Killed caffeinate process");
} catch (ex: any) {
this.log.error(`Failed to kill caffeinate process! ${ex.message}`);
} finally {
this.isCaffeinated = false;
this.isCaffeinated = false; // Reset the caffeinated state
}
}
}

/**
* Method to let us know that the caffeinate process has ended
*
* @param _ The message returned by the EventEmitter
* @param code The exit code or signal that caused the process to exit
*/
private onClose(code: any) {
this.log.warn(`Caffeinate process closed with code: ${code}. Restarting...`);
this.isCaffeinated = false;
this.start(); // Restart caffeinate to maintain wakefulness
}

/**
* Method to handle when the caffeinate process exits
*
* @param code The exit code or signal that caused the process to exit
*/
private onExit(code: any) {
this.log.warn(`Caffeinate process exited with code: ${code}. Restarting...`);
this.isCaffeinated = false;
this.start(); // Restart caffeinate to maintain wakefulness
}

/**
* Method to handle when the caffeinate process disconnects
*/
private onDisconnect() {
this.log.warn("Caffeinate process disconnected. Restarting...");
this.isCaffeinated = false;
this.start(); // Restart caffeinate to maintain wakefulness
}

/**
* Method to handle errors in the caffeinate process
*
* @param err The error encountered by the child process
*/
private onClose(msg: any) {
private onError(err: any) {
this.log.error(`Caffeinate process encountered an error: ${err.message}. Restarting...`);
this.isCaffeinated = false;
this.start(); // Restart caffeinate to maintain wakefulness
}
}