federer
Version:
Experiments in asynchronous federated learning and decentralized learning
87 lines • 3.48 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProcessTracker = void 0;
const assert = require("assert");
/**
* Keeps track of spawned child processes.
*/
class ProcessTracker {
constructor(logger) {
this.logger = logger;
/** The child processes that have been spawned by the process tracker. */
this.processes = new Set();
/**
* Whether the current process tracker is "spent", meaning that it cannot be
* used anymore.
*/
this.spent = false;
this.sigintListener = () => this.cleanExit("Received SIGINT");
this.sigtermListener = () => this.cleanExit("Received SIGTERM");
this.uncaughtExceptionListener = (err) => {
this.killChildProcesses();
this.logger.error("Coordinator crashed");
this.logger.error(err.message);
this.logger.error(err.stack);
// Usually, we disallow calls to `console` methods, preferring to use a
// logger. But when the process is about to crash, we want the developer to
// know about it; as a last ditch effort to get the programmer's attention
// about this crash, we also log to the console. This is the only exception
// to the rule.
// eslint-disable-next-line no-console
console.error(err);
process.exit(1);
};
this.addListeners();
}
addListeners() {
// Catch CTRL+C signal
process.on("SIGINT", this.sigintListener);
// Catch kill signal
process.on("SIGTERM", this.sigtermListener);
// Kill children when coordinator crashes
process.on("uncaughtException", this.uncaughtExceptionListener);
}
/** Send SIGTERM to all child processes. */
killChildProcesses() {
this.checkNotSpent();
this.processes.forEach((child) => child.kill());
}
/** Promise resolving once all children have exited. */
async allChildrenExited() {
await Promise.all([...this.processes.values()].map((child) => child.exited()));
}
/**
* Removes the registered event listeners. After this method has been called,
* the ProcessTracker is "spent": it should not be used anymore.
*/
removeListeners() {
this.checkNotSpent();
process.removeListener("SIGINT", this.sigintListener);
process.removeListener("SIGTERM", this.sigtermListener);
process.removeListener("uncaughtException", this.uncaughtExceptionListener);
this.spent = true;
}
/**
* Register a child with the ProcessTracker.
*
* @param child Process representing the child process to track
* @param name Name of the process, used for logging
*/
register(child, name) {
this.processes.add(child);
child.events.on("exited", (reason) => {
this.logger.error(`Child ${name} exited for reason '${reason}'`);
this.processes.delete(child);
});
}
cleanExit(reason) {
this.logger.info(`[Coordinator] Killing child processes and exiting because: '${reason}'`);
this.killChildProcesses();
process.exit(0);
}
checkNotSpent() {
assert(!this.spent, "After ProcessTracker.removeListeners() has been called, the ProcessTracker cannot be used anymore");
}
}
exports.ProcessTracker = ProcessTracker;
//# sourceMappingURL=ProcessTracker.js.map