719 lines
21 KiB
JavaScript
719 lines
21 KiB
JavaScript
"use strict";
|
|
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
// file that has been converted to a CommonJS file using a Babel-
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
mod
|
|
));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var process_manager_exports = {};
|
|
__export(process_manager_exports, {
|
|
ProcessManager: () => ProcessManager,
|
|
QueryProcessManager: () => QueryProcessManager,
|
|
QueryProcessWrapper: () => QueryProcessWrapper,
|
|
RawProcessManager: () => RawProcessManager,
|
|
RawProcessWrapper: () => RawProcessWrapper,
|
|
StreamProcessManager: () => StreamProcessManager,
|
|
StreamProcessWrapper: () => StreamProcessWrapper,
|
|
StreamWorker: () => StreamWorker,
|
|
exec: () => exec,
|
|
processManagers: () => processManagers
|
|
});
|
|
module.exports = __toCommonJS(process_manager_exports);
|
|
var child_process = __toESM(require("child_process"));
|
|
var cluster = __toESM(require("cluster"));
|
|
var path = __toESM(require("path"));
|
|
var Streams = __toESM(require("./streams"));
|
|
var import_fs = require("./fs");
|
|
/**
|
|
* Process Manager
|
|
* Pokemon Showdown - http://pokemonshowdown.com/
|
|
*
|
|
* This file abstract out multiprocess logic involved in several tasks.
|
|
*
|
|
* Child processes can be queried.
|
|
*
|
|
* @license MIT
|
|
*/
|
|
const processManagers = [];
|
|
function exec(args, execOptions) {
|
|
if (Array.isArray(args)) {
|
|
const cmd = args.shift();
|
|
if (!cmd)
|
|
throw new Error(`You must pass a command to ProcessManager.exec.`);
|
|
return new Promise((resolve, reject) => {
|
|
child_process.execFile(cmd, args, execOptions, (err, stdout, stderr) => {
|
|
if (err)
|
|
reject(err);
|
|
if (typeof stdout !== "string")
|
|
stdout = stdout.toString();
|
|
if (typeof stderr !== "string")
|
|
stderr = stderr.toString();
|
|
resolve({ stdout, stderr });
|
|
});
|
|
});
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
child_process.exec(args, execOptions, (error, stdout, stderr) => {
|
|
if (error)
|
|
reject(error);
|
|
if (typeof stdout !== "string")
|
|
stdout = stdout.toString();
|
|
resolve(stdout);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
class SubprocessStream extends Streams.ObjectReadWriteStream {
|
|
constructor(process2, taskId2) {
|
|
super();
|
|
this.process = process2;
|
|
this.taskId = taskId2;
|
|
this.process.process.send(`${taskId2}
|
|
NEW`);
|
|
}
|
|
_write(message2) {
|
|
if (!this.process.process.connected) {
|
|
this.pushError(new Error(`Process disconnected (possibly crashed?)`));
|
|
return;
|
|
}
|
|
this.process.process.send(`${this.taskId}
|
|
WRITE
|
|
${message2}`);
|
|
}
|
|
_writeEnd() {
|
|
this.process.process.send(`${this.taskId}
|
|
WRITEEND`);
|
|
}
|
|
_destroy() {
|
|
if (!this.process.process.connected)
|
|
return;
|
|
this.process.process.send(`${this.taskId}
|
|
DESTROY`);
|
|
this.process.deleteStream(this.taskId);
|
|
this.process = null;
|
|
}
|
|
}
|
|
class RawSubprocessStream extends Streams.ObjectReadWriteStream {
|
|
constructor(process2) {
|
|
super();
|
|
this.process = process2;
|
|
}
|
|
_write(message2) {
|
|
if (!this.process.getProcess().connected) {
|
|
return;
|
|
}
|
|
this.process.process.send(message2);
|
|
}
|
|
}
|
|
class QueryProcessWrapper {
|
|
constructor(file, messageCallback) {
|
|
this.process = child_process.fork(file, [], { cwd: import_fs.FS.ROOT_PATH });
|
|
this.taskId = 0;
|
|
this.file = file;
|
|
this.pendingTasks = /* @__PURE__ */ new Map();
|
|
this.pendingRelease = null;
|
|
this.resolveRelease = null;
|
|
this.messageCallback = messageCallback || null;
|
|
this.process.on("message", (message2) => {
|
|
if (message2.startsWith("THROW\n")) {
|
|
const error = new Error();
|
|
error.stack = message2.slice(6);
|
|
throw error;
|
|
}
|
|
if (message2.startsWith("DEBUG\n")) {
|
|
this.debug = message2.slice(6);
|
|
return;
|
|
}
|
|
if (this.messageCallback && message2.startsWith(`CALLBACK
|
|
`)) {
|
|
this.messageCallback(message2.slice(9));
|
|
return;
|
|
}
|
|
const nlLoc2 = message2.indexOf("\n");
|
|
if (nlLoc2 <= 0)
|
|
throw new Error(`Invalid response ${message2}`);
|
|
const taskId2 = parseInt(message2.slice(0, nlLoc2));
|
|
const resolve = this.pendingTasks.get(taskId2);
|
|
if (!resolve)
|
|
throw new Error(`Invalid taskId ${message2.slice(0, nlLoc2)}`);
|
|
this.pendingTasks.delete(taskId2);
|
|
const resp = this.safeJSON(message2.slice(nlLoc2 + 1));
|
|
resolve(resp);
|
|
if (this.resolveRelease && !this.getLoad())
|
|
this.destroy();
|
|
});
|
|
}
|
|
safeJSON(obj) {
|
|
if (obj === "undefined") {
|
|
return void 0;
|
|
}
|
|
try {
|
|
return JSON.parse(obj);
|
|
} catch (e) {
|
|
global.Monitor?.crashlog?.(e, `a ${path.basename(this.file)} process`, { result: obj });
|
|
return void 0;
|
|
}
|
|
}
|
|
getProcess() {
|
|
return this.process;
|
|
}
|
|
getLoad() {
|
|
return this.pendingTasks.size;
|
|
}
|
|
query(input) {
|
|
this.taskId++;
|
|
const taskId2 = this.taskId;
|
|
this.process.send(`${taskId2}
|
|
${JSON.stringify(input)}`);
|
|
return new Promise((resolve) => {
|
|
this.pendingTasks.set(taskId2, resolve);
|
|
});
|
|
}
|
|
release() {
|
|
if (this.pendingRelease)
|
|
return this.pendingRelease;
|
|
if (!this.getLoad()) {
|
|
this.destroy();
|
|
} else {
|
|
this.pendingRelease = new Promise((resolve) => {
|
|
this.resolveRelease = resolve;
|
|
});
|
|
}
|
|
return this.pendingRelease;
|
|
}
|
|
destroy() {
|
|
if (this.pendingRelease && !this.resolveRelease) {
|
|
return;
|
|
}
|
|
this.process.disconnect();
|
|
for (const resolver of this.pendingTasks.values()) {
|
|
resolver("");
|
|
}
|
|
this.pendingTasks.clear();
|
|
if (this.resolveRelease) {
|
|
this.resolveRelease();
|
|
this.resolveRelease = null;
|
|
} else if (!this.pendingRelease) {
|
|
this.pendingRelease = Promise.resolve();
|
|
}
|
|
}
|
|
}
|
|
class StreamProcessWrapper {
|
|
constructor(file, messageCallback) {
|
|
this.taskId = 0;
|
|
this.activeStreams = /* @__PURE__ */ new Map();
|
|
this.pendingRelease = null;
|
|
this.resolveRelease = null;
|
|
this.process = child_process.fork(file, [], { cwd: import_fs.FS.ROOT_PATH });
|
|
this.messageCallback = messageCallback;
|
|
this.process.on("message", (message2) => {
|
|
if (message2.startsWith("THROW\n")) {
|
|
const error = new Error();
|
|
error.stack = message2.slice(6);
|
|
throw error;
|
|
}
|
|
if (this.messageCallback && message2.startsWith(`CALLBACK
|
|
`)) {
|
|
this.messageCallback(message2.slice(9));
|
|
return;
|
|
}
|
|
if (message2.startsWith("DEBUG\n")) {
|
|
this.setDebug(message2.slice(6));
|
|
return;
|
|
}
|
|
let nlLoc2 = message2.indexOf("\n");
|
|
if (nlLoc2 <= 0)
|
|
throw new Error(`Invalid response ${message2}`);
|
|
const taskId2 = parseInt(message2.slice(0, nlLoc2));
|
|
const stream2 = this.activeStreams.get(taskId2);
|
|
if (!stream2)
|
|
return;
|
|
message2 = message2.slice(nlLoc2 + 1);
|
|
nlLoc2 = message2.indexOf("\n");
|
|
if (nlLoc2 < 0)
|
|
nlLoc2 = message2.length;
|
|
const messageType2 = message2.slice(0, nlLoc2);
|
|
message2 = message2.slice(nlLoc2 + 1);
|
|
if (messageType2 === "END") {
|
|
stream2.pushEnd();
|
|
this.deleteStream(taskId2);
|
|
return;
|
|
} else if (messageType2 === "PUSH") {
|
|
stream2.push(message2);
|
|
} else if (messageType2 === "THROW") {
|
|
const error = new Error();
|
|
error.stack = message2;
|
|
stream2.pushError(error, true);
|
|
} else {
|
|
throw new Error(`Unrecognized messageType ${messageType2}`);
|
|
}
|
|
});
|
|
}
|
|
setDebug(message2) {
|
|
this.debug = (this.debug || "").slice(-32768) + "\n=====\n" + message2;
|
|
}
|
|
getLoad() {
|
|
return this.activeStreams.size;
|
|
}
|
|
getProcess() {
|
|
return this.process;
|
|
}
|
|
deleteStream(taskId2) {
|
|
this.activeStreams.delete(taskId2);
|
|
if (this.resolveRelease && !this.getLoad())
|
|
void this.destroy();
|
|
}
|
|
createStream() {
|
|
this.taskId++;
|
|
const taskId2 = this.taskId;
|
|
const stream2 = new SubprocessStream(this, taskId2);
|
|
this.activeStreams.set(taskId2, stream2);
|
|
return stream2;
|
|
}
|
|
release() {
|
|
if (this.pendingRelease)
|
|
return this.pendingRelease;
|
|
if (!this.getLoad()) {
|
|
void this.destroy();
|
|
} else {
|
|
this.pendingRelease = new Promise((resolve) => {
|
|
this.resolveRelease = resolve;
|
|
});
|
|
}
|
|
return this.pendingRelease;
|
|
}
|
|
destroy() {
|
|
if (this.pendingRelease && !this.resolveRelease) {
|
|
return;
|
|
}
|
|
this.process.disconnect();
|
|
const destroyed = [];
|
|
for (const stream2 of this.activeStreams.values()) {
|
|
destroyed.push(stream2.destroy());
|
|
}
|
|
this.activeStreams.clear();
|
|
if (this.resolveRelease) {
|
|
this.resolveRelease();
|
|
this.resolveRelease = null;
|
|
} else if (!this.pendingRelease) {
|
|
this.pendingRelease = Promise.resolve();
|
|
}
|
|
return Promise.all(destroyed);
|
|
}
|
|
}
|
|
class StreamWorker {
|
|
constructor(stream2) {
|
|
this.load = 0;
|
|
this.workerid = 0;
|
|
this.stream = stream2;
|
|
}
|
|
}
|
|
class RawProcessWrapper {
|
|
constructor(file, isCluster, env) {
|
|
this.taskId = 0;
|
|
this.pendingRelease = null;
|
|
this.resolveRelease = null;
|
|
this.workerid = 0;
|
|
/** Not managed by RawProcessWrapper itself */
|
|
this.load = 0;
|
|
if (isCluster) {
|
|
this.process = cluster.fork(env);
|
|
this.workerid = this.process.id;
|
|
} else {
|
|
this.process = child_process.fork(file, [], { cwd: import_fs.FS.ROOT_PATH, env });
|
|
}
|
|
this.process.on("message", (message2) => {
|
|
this.stream.push(message2);
|
|
});
|
|
this.stream = new RawSubprocessStream(this);
|
|
}
|
|
setDebug(message2) {
|
|
this.debug = (this.debug || "").slice(-32768) + "\n=====\n" + message2;
|
|
}
|
|
getLoad() {
|
|
return this.load;
|
|
}
|
|
getProcess() {
|
|
return this.process.process ? this.process.process : this.process;
|
|
}
|
|
release() {
|
|
if (this.pendingRelease)
|
|
return this.pendingRelease;
|
|
if (!this.getLoad()) {
|
|
void this.destroy();
|
|
} else {
|
|
this.pendingRelease = new Promise((resolve) => {
|
|
this.resolveRelease = resolve;
|
|
});
|
|
}
|
|
return this.pendingRelease;
|
|
}
|
|
destroy() {
|
|
if (this.pendingRelease && !this.resolveRelease) {
|
|
return;
|
|
}
|
|
void this.stream.destroy();
|
|
this.process.disconnect();
|
|
return;
|
|
}
|
|
}
|
|
const _ProcessManager = class {
|
|
constructor(module2) {
|
|
this.processes = [];
|
|
this.releasingProcesses = [];
|
|
this.crashedProcesses = [];
|
|
this.crashTime = 0;
|
|
this.crashRespawnCount = 0;
|
|
this.module = module2;
|
|
this.filename = module2.filename;
|
|
this.basename = path.basename(module2.filename);
|
|
this.isParentProcess = process.mainModule !== module2 || !process.send;
|
|
this.listen();
|
|
}
|
|
acquire() {
|
|
if (!this.processes.length) {
|
|
return null;
|
|
}
|
|
let lowestLoad = this.processes[0];
|
|
for (const process2 of this.processes) {
|
|
if (process2.getLoad() < lowestLoad.getLoad()) {
|
|
lowestLoad = process2;
|
|
}
|
|
}
|
|
return lowestLoad;
|
|
}
|
|
releaseCrashed(process2) {
|
|
const index = this.processes.indexOf(process2);
|
|
if (index < 0)
|
|
return;
|
|
this.processes.splice(index, 1);
|
|
this.destroyProcess(process2);
|
|
void process2.release().then(() => {
|
|
const releasingIndex = this.releasingProcesses.indexOf(process2);
|
|
if (releasingIndex >= 0) {
|
|
this.releasingProcesses.splice(releasingIndex, 1);
|
|
}
|
|
});
|
|
const now = Date.now();
|
|
if (this.crashTime && now - this.crashTime > 30 * 60 * 1e3) {
|
|
this.crashTime = 0;
|
|
this.crashRespawnCount = 0;
|
|
}
|
|
if (!this.crashTime)
|
|
this.crashTime = now;
|
|
this.crashRespawnCount += 1;
|
|
void Promise.reject(
|
|
new Error(`Process ${this.basename} ${process2.getProcess().pid} crashed and had to be restarted`)
|
|
);
|
|
this.releasingProcesses.push(process2);
|
|
this.crashedProcesses.push(process2);
|
|
if (this.crashRespawnCount <= 5) {
|
|
this.spawn(this.processes.length + 1);
|
|
}
|
|
}
|
|
unspawn() {
|
|
return Promise.all([...this.processes].map(
|
|
(process2) => this.unspawnOne(process2)
|
|
));
|
|
}
|
|
async unspawnOne(process2) {
|
|
if (!process2)
|
|
return;
|
|
this.destroyProcess(process2);
|
|
const processIndex = this.processes.indexOf(process2);
|
|
if (processIndex < 0)
|
|
throw new Error("Process inactive");
|
|
this.processes.splice(this.processes.indexOf(process2), 1);
|
|
this.releasingProcesses.push(process2);
|
|
await process2.release();
|
|
const index = this.releasingProcesses.indexOf(process2);
|
|
if (index < 0)
|
|
return;
|
|
this.releasingProcesses.splice(index, 1);
|
|
}
|
|
spawn(count = 1, force) {
|
|
if (!this.isParentProcess)
|
|
return;
|
|
if (_ProcessManager.disabled && !force)
|
|
return;
|
|
const spawnCount = count - this.processes.length;
|
|
for (let i = 0; i < spawnCount; i++) {
|
|
this.spawnOne(force);
|
|
}
|
|
}
|
|
spawnOne(force) {
|
|
if (!this.isParentProcess)
|
|
throw new Error("Must use in parent process");
|
|
if (_ProcessManager.disabled && !force)
|
|
return null;
|
|
const process2 = this.createProcess();
|
|
process2.process.on("disconnect", () => this.releaseCrashed(process2));
|
|
this.processes.push(process2);
|
|
return process2;
|
|
}
|
|
respawn(count = null) {
|
|
if (count === null)
|
|
count = this.processes.length;
|
|
const unspawned = this.unspawn();
|
|
this.spawn(count);
|
|
return unspawned;
|
|
}
|
|
destroyProcess(process2) {
|
|
}
|
|
destroy() {
|
|
const index = processManagers.indexOf(this);
|
|
if (index >= 0)
|
|
processManagers.splice(index, 1);
|
|
return this.unspawn();
|
|
}
|
|
};
|
|
let ProcessManager = _ProcessManager;
|
|
ProcessManager.disabled = false;
|
|
class QueryProcessManager extends ProcessManager {
|
|
/**
|
|
* @param timeout The number of milliseconds to wait before terminating a query. Defaults to 900000 ms (15 minutes).
|
|
*/
|
|
constructor(module2, query, timeout = 15 * 60 * 1e3, debugCallback) {
|
|
super(module2);
|
|
this._query = query;
|
|
this.timeout = timeout;
|
|
this.messageCallback = debugCallback;
|
|
processManagers.push(this);
|
|
}
|
|
async query(input, process2 = this.acquire()) {
|
|
if (!process2)
|
|
return this._query(input);
|
|
const timeout = setTimeout(() => {
|
|
const debugInfo = process2.debug || "No debug information found.";
|
|
process2.destroy();
|
|
throw new Error(
|
|
`A query originating in ${this.basename} took too long to complete; the process has been killed.
|
|
${debugInfo}`
|
|
);
|
|
}, this.timeout);
|
|
const result = await process2.query(input);
|
|
clearTimeout(timeout);
|
|
return result;
|
|
}
|
|
queryTemporaryProcess(input, force) {
|
|
const process2 = this.spawnOne(force);
|
|
const result = this.query(input, process2);
|
|
void this.unspawnOne(process2);
|
|
return result;
|
|
}
|
|
createProcess() {
|
|
return new QueryProcessWrapper(this.filename, this.messageCallback);
|
|
}
|
|
listen() {
|
|
if (this.isParentProcess)
|
|
return;
|
|
process.on("message", (message) => {
|
|
const nlLoc = message.indexOf("\n");
|
|
if (nlLoc <= 0)
|
|
throw new Error(`Invalid response ${message}`);
|
|
const taskId = message.slice(0, nlLoc);
|
|
message = message.slice(nlLoc + 1);
|
|
if (taskId.startsWith("EVAL")) {
|
|
process.send(`${taskId}
|
|
` + eval(message));
|
|
return;
|
|
}
|
|
void Promise.resolve(this._query(JSON.parse(message))).then(
|
|
(response) => process.send(`${taskId}
|
|
${JSON.stringify(response)}`)
|
|
);
|
|
});
|
|
process.on("disconnect", () => {
|
|
process.exit();
|
|
});
|
|
}
|
|
}
|
|
class StreamProcessManager extends ProcessManager {
|
|
constructor(module2, createStream, messageCallback) {
|
|
super(module2);
|
|
this.activeStreams = /* @__PURE__ */ new Map();
|
|
this._createStream = createStream;
|
|
this.messageCallback = messageCallback;
|
|
processManagers.push(this);
|
|
}
|
|
createStream() {
|
|
const process2 = this.acquire();
|
|
if (!process2)
|
|
return this._createStream();
|
|
return process2.createStream();
|
|
}
|
|
createProcess() {
|
|
return new StreamProcessWrapper(this.filename, this.messageCallback);
|
|
}
|
|
async pipeStream(taskId2, stream2) {
|
|
let done = false;
|
|
while (!done) {
|
|
try {
|
|
let value;
|
|
({ value, done } = await stream2.next());
|
|
process.send(`${taskId2}
|
|
PUSH
|
|
${value}`);
|
|
} catch (err) {
|
|
process.send(`${taskId2}
|
|
THROW
|
|
${err.stack}`);
|
|
}
|
|
}
|
|
if (!this.activeStreams.has(taskId2)) {
|
|
return;
|
|
}
|
|
process.send(`${taskId2}
|
|
END`);
|
|
this.activeStreams.delete(taskId2);
|
|
}
|
|
listen() {
|
|
if (this.isParentProcess)
|
|
return;
|
|
process.on("message", (message) => {
|
|
let nlLoc = message.indexOf("\n");
|
|
if (nlLoc <= 0)
|
|
throw new Error(`Invalid request ${message}`);
|
|
const taskId = message.slice(0, nlLoc);
|
|
const stream = this.activeStreams.get(taskId);
|
|
message = message.slice(nlLoc + 1);
|
|
nlLoc = message.indexOf("\n");
|
|
if (nlLoc < 0)
|
|
nlLoc = message.length;
|
|
const messageType = message.slice(0, nlLoc);
|
|
message = message.slice(nlLoc + 1);
|
|
if (taskId.startsWith("EVAL")) {
|
|
process.send(`${taskId}
|
|
` + eval(message));
|
|
return;
|
|
}
|
|
if (messageType === "NEW") {
|
|
if (stream)
|
|
throw new Error(`NEW: taskId ${taskId} already exists`);
|
|
const newStream = this._createStream();
|
|
this.activeStreams.set(taskId, newStream);
|
|
void this.pipeStream(taskId, newStream);
|
|
} else if (messageType === "DESTROY") {
|
|
if (!stream)
|
|
throw new Error(`DESTROY: Invalid taskId ${taskId}`);
|
|
void stream.destroy();
|
|
this.activeStreams.delete(taskId);
|
|
} else if (messageType === "WRITE") {
|
|
if (!stream)
|
|
throw new Error(`WRITE: Invalid taskId ${taskId}`);
|
|
void stream.write(message);
|
|
} else if (messageType === "WRITEEND") {
|
|
if (!stream)
|
|
throw new Error(`WRITEEND: Invalid taskId ${taskId}`);
|
|
void stream.writeEnd();
|
|
} else {
|
|
throw new Error(`Unrecognized messageType ${messageType}`);
|
|
}
|
|
});
|
|
process.on("disconnect", () => {
|
|
process.exit();
|
|
});
|
|
}
|
|
}
|
|
class RawProcessManager extends ProcessManager {
|
|
constructor(options) {
|
|
super(options.module);
|
|
/** full list of processes - parent process only */
|
|
this.workers = [];
|
|
/** if spawning 0 worker processes, the worker is instead stored here in the parent process */
|
|
this.masterWorker = null;
|
|
/** stream used only in the child process */
|
|
this.activeStream = null;
|
|
this.spawnSubscription = null;
|
|
this.unspawnSubscription = null;
|
|
/** worker ID of cluster worker - cluster child process only (0 otherwise) */
|
|
this.workerid = cluster.worker?.id || 0;
|
|
this.isCluster = !!options.isCluster;
|
|
this._setupChild = options.setupChild;
|
|
this.env = options.env;
|
|
if (this.isCluster && this.isParentProcess) {
|
|
cluster.setupMaster({
|
|
exec: this.filename,
|
|
// @ts-ignore TODO: update type definition
|
|
cwd: import_fs.FS.ROOT_PATH
|
|
});
|
|
}
|
|
processManagers.push(this);
|
|
}
|
|
subscribeSpawn(callback) {
|
|
this.spawnSubscription = callback;
|
|
}
|
|
subscribeUnspawn(callback) {
|
|
this.unspawnSubscription = callback;
|
|
}
|
|
spawn(count) {
|
|
super.spawn(count);
|
|
if (!this.workers.length) {
|
|
this.masterWorker = new StreamWorker(this._setupChild());
|
|
this.workers.push(this.masterWorker);
|
|
this.spawnSubscription?.(this.masterWorker);
|
|
}
|
|
}
|
|
createProcess() {
|
|
const process2 = new RawProcessWrapper(this.filename, this.isCluster, this.env);
|
|
this.workers.push(process2);
|
|
this.spawnSubscription?.(process2);
|
|
return process2;
|
|
}
|
|
destroyProcess(process2) {
|
|
const index = this.workers.indexOf(process2);
|
|
if (index >= 0)
|
|
this.workers.splice(index, 1);
|
|
this.unspawnSubscription?.(process2);
|
|
}
|
|
async pipeStream(stream2) {
|
|
let done = false;
|
|
while (!done) {
|
|
try {
|
|
let value;
|
|
({ value, done } = await stream2.next());
|
|
process.send(value);
|
|
} catch (err) {
|
|
process.send(`THROW
|
|
${err.stack}`);
|
|
}
|
|
}
|
|
}
|
|
listen() {
|
|
if (this.isParentProcess)
|
|
return;
|
|
setImmediate(() => {
|
|
this.activeStream = this._setupChild();
|
|
void this.pipeStream(this.activeStream);
|
|
});
|
|
process.on("message", (message2) => {
|
|
void this.activeStream.write(message2);
|
|
});
|
|
process.on("disconnect", () => {
|
|
process.exit();
|
|
});
|
|
}
|
|
}
|
|
//# sourceMappingURL=process-manager.js.map
|