/*
* Ensures there's only a single instance of GPII.
*
* Copyright 2017 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The R&D leading to these results received funding from the
* Department of Education - Grant H421A150005 (GPII-APCP). However,
* these results do not necessarily represent the policy of the
* Department of Education, and you should not assume endorsement by the
* Federal Government.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
"use strict";
var fluid = require("infusion");
var fs = require("fs");
var gpii = fluid.registerNamespace("gpii");
fluid.registerNamespace("gpii.singleInstance");
/**
* Declares this instance of GPII to be the only instance, unless there's already one running.
*
* The "pid/lock file" method is used, where a file containing the pid of the current process is created. If it
* already exists, and the pid points to a running GPII instance, then the lock will fail.
*
* File IO is performed synchronously, ensuring the checks are made before GPII does anything else (eg, bind to a port).
*
* @todo More checks need to be made to ensure the pid is GPII, and not just another process.
*
* @param pidFile {String} [Optional] The path of the pid file. Default is "gpii.pid" in the settings directory.
* @return {boolean} true if this instance is the only GPII instance.
*/
gpii.singleInstance.registerInstance = function (pidFile) {
if (!pidFile || typeof(pidFile) !== "string") {
pidFile = gpii.singleInstance.getDefaultPidFile();
}
var gpiiPid = gpii.singleInstance.checkInstance(pidFile);
if (gpiiPid === null) {
// Write the pid, but fail if the file exists. The stale one was deleted, but there might be another
// GPII instance just starting.
fs.writeFileSync(pidFile, process.pid, { flag: "wx" });
}
var success = !gpiiPid || gpiiPid === process.pid;
if (!success) {
fluid.log(fluid.logLevel.WARN, "There's already an instance of GPII detected.");
}
return success;
};
fluid.defaults("gpii.singleInstance.registerInstance", {
gradeNames: "fluid.function"
});
/**
* Checks for another GPII instance.
*
* @param pidFile {String} [Optional] The path of the pid file. Default is "gpii.pid" in the settings directory.
* @returns {number} non-zero PID of the other GPII process (including this process), or null if this is the only one.
*/
gpii.singleInstance.checkInstance = function (pidFile) {
Iif (!pidFile || typeof(pidFile) !== "string") {
pidFile = gpii.singleInstance.getDefaultPidFile();
}
var pid = null;
try {
// Get the old pid from the lock file
var content = fs.readFileSync(pidFile, {encoding: "utf8"});
pid = parseInt(content);
// A "0" PID will never be GPII (and process.kill will succeed on Linux).
if (pid && pid !== process.pid) {
try {
// Check if it's still running.
process.kill(pid, 0);
// No error means the process is running.
// TODO: Ensure the process really is GPII, and not just a re-use of the pid.
} catch (e) {
// Process isn't running (or permission denied, which suggests it's not GPII).
pid = null;
}
}
if (!pid) {
// Remove the pid file, as it's stale/invalid.
fs.unlinkSync(pidFile);
}
} catch (e) {
// The file doesn't exist.
}
return pid || null;
};
fluid.defaults("gpii.singleInstance.checkInstance", {
gradeNames: "fluid.function"
});
/**
* Prevents the current GPII instance from being the only one, called when the GPII process is being shutdown.
*
* @param pidFile
* @return {Boolean} true if this instance was de-registered, false if it wasn't registered.
*/
gpii.singleInstance.deregisterInstance = function (pidFile) {
if (!pidFile || typeof(pidFile) !== "string") {
pidFile = gpii.singleInstance.getDefaultPidFile();
}
var success = false;
var gpiiPid = gpii.singleInstance.checkInstance(pidFile);
if (gpiiPid === process.pid) {
success = true;
fs.unlinkSync(pidFile);
}
return success;
};
fluid.defaults("gpii.singleInstance.deregisterInstance", {
gradeNames: "fluid.function"
});
/**
* Get the default pid file path, "<settings dir>/gpii.pid".
*/
gpii.singleInstance.getDefaultPidFile = function () {
var settingsDirComponent = gpii.settingsDir();
var pidFile = settingsDirComponent.getGpiiSettingsDir() + "/gpii.pid";
settingsDirComponent.destroy();
return pidFile;
};
|