You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/hydrooj/src/loader.ts

175 lines
5.7 KiB
TypeScript

5 years ago
/* eslint-disable import/no-dynamic-require */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-eval */
import 'reflect-metadata';
import './init';
import './interface';
import os from 'os';
import path from 'path';
import cluster from 'cluster';
import fs from 'fs-extra';
import './utils';
import cac from 'cac';
import { Logger } from './logger';
4 years ago
import './ui';
import * as bus from './service/bus';
export * from './interface';
const argv = cac().parse();
if (argv.options.debug) {
process.env.NODE_ENV = 'development';
process.env.DEV = 'on';
} else process.env.NODE_ENV = process.env.NODE_ENV || 'production';
const logger = new Logger('loader');
logger.debug('%o', argv);
async function fork(args: string[] = []) {
const _args = process.argv.slice(2);
4 years ago
_args.push(...args, `--addons=${Buffer.from(JSON.stringify(global.addons)).toString('base64')}`);
cluster.setupMaster({ args: _args });
return cluster.fork();
}
4 years ago
interface EntryConfig {
entry: string,
newProcess?: boolean,
}
async function entry(config: EntryConfig) {
4 years ago
if (config.entry) {
if (config.newProcess) {
const p = await fork([`--entry=${config.entry}`]);
4 years ago
await new Promise((resolve, reject) => {
p.on('exit', (code, signal) => {
4 years ago
if (code === 0) resolve(null);
else reject(signal);
});
p.on('error', (err: Error) => {
4 years ago
p.kill();
reject(err);
});
});
} else {
const loader = require(`./entry/${config.entry}`);
4 years ago
return await loader.load(entry, global.addons);
}
4 years ago
}
return null;
4 years ago
}
4 years ago
export type Entry = typeof entry;
4 years ago
async function stopWorker() {
cluster.disconnect();
}
async function startWorker(cnt: number, createFirst = true) {
3 years ago
if (argv.options.single) {
await entry({ entry: 'worker' });
} else {
await fork(createFirst ? ['--firstWorker'] : undefined);
for (let i = 1; i < cnt; i++) await fork();
}
4 years ago
}
async function reload(count = 1) {
logger.info('Reloading');
await stopWorker();
logger.info('Worker stopped');
await startWorker(count);
}
4 years ago
const shell = new Logger('shell');
async function executeCommand(input: string) {
input = input.trim();
4 years ago
// Clear the stack
setImmediate(async () => {
if (input === 'restart') return reload();
if (input === 'exit' || input === 'quit' || input === 'shutdown') {
return process.kill(process.pid, 'SIGINT');
}
4 years ago
try {
// eslint-disable-next-line no-eval
shell.info(await eval(input));
4 years ago
} catch (e) {
shell.warn(e);
}
return true;
});
4 years ago
}
4 years ago
bus.on('message/reload', reload);
bus.on('message/run', executeCommand);
process.on('unhandledRejection', logger.error);
process.on('uncaughtException', logger.error);
4 years ago
const publicTemp = path.resolve(os.tmpdir(), 'hydro', 'public');
export function addon(addonPath: string, prepend = false) {
4 years ago
if (!(fs.existsSync(addonPath) && fs.statSync(addonPath).isFile())) {
4 years ago
try {
// Is a npm package
const packagejson = require.resolve(`${addonPath}/package.json`);
const modulePath = path.dirname(packagejson);
4 years ago
const publicPath = path.resolve(modulePath, 'public');
if (fs.existsSync(publicPath)) fs.copySync(publicPath, publicTemp);
if (prepend) global.addons.unshift(modulePath);
else global.addons.push(modulePath);
4 years ago
} catch (e) {
logger.error(`Addon not found: ${addonPath}`);
4 years ago
}
} else logger.error(`Addon not found: ${addonPath}`);
4 years ago
}
export async function load() {
addon(path.resolve(__dirname, '..'), true);
4 years ago
Error.stackTraceLimit = 50;
3 years ago
if (cluster.isMaster || argv.options.startAsMaster) {
logger.info(`Master ${process.pid} Starting`);
const cnt = await entry({ entry: 'master' });
logger.info('Master started');
cluster.on('exit', (worker, code, signal) => {
logger.warn(`Worker ${worker.process.pid} ${worker.id} exit: ${code} ${signal}`);
if (code) startWorker(1);
});
cluster.on('disconnect', (worker) => {
logger.info(`Worker ${worker.process.pid} ${worker.id} disconnected`);
});
cluster.on('listening', (worker, address) => {
logger.success(`Worker ${worker.process.pid} ${worker.id} listening at `, address);
});
cluster.on('online', (worker) => {
logger.success(`Worker ${worker.process.pid} ${worker.id} is online`);
});
await startWorker(cnt);
} else {
3 years ago
global.addons = JSON.parse(Buffer.from(argv.options.addons as string, 'base64').toString());
logger.info('%o', global.addons);
3 years ago
if (argv.options.entry) {
logger.info(`Worker ${process.pid} Starting as ${argv.options.entry}`);
await entry({ entry: argv.options.entry });
logger.success(`Worker ${process.pid} Started as ${argv.options.entry}`);
4 years ago
} else {
3 years ago
if (argv.options.firstWorker) global.Hydro.isFirstWorker = true;
else global.Hydro.isFirstWorker = false;
logger.info(`Worker ${process.pid} Starting`);
4 years ago
await entry({ entry: 'worker' });
logger.success(`Worker ${process.pid} Started`);
4 years ago
}
}
4 years ago
if (global.gc) global.gc();
}
4 years ago
export async function loadCli() {
await entry({ entry: 'cli' });
4 years ago
process.kill(process.pid, 'SIGINT');
4 years ago
}
3 years ago
if (argv.options.pandora || require.main === module) {
const func = argv.args[0] === 'cli' ? load : loadCli;
4 years ago
func().catch((e) => {
logger.error(e);
process.exit(1);
});
}