diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c00e3487b..d66906ba6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,8 +22,8 @@ jobs: yarn build:production - uses: actions/upload-artifact@v1 with: - name: development.js - path: .build/development.js + name: app.js + path: .build/app.js - uses: actions/upload-artifact@v1 with: name: full.js diff --git a/README.md b/README.md index b79903715..e6e619f56 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Hydro 的界面基于 Vijos 二次开发。 #### 直接部署: 安装mongodb (省略) -下载Github Actions自动编译的 development.js 直接运行即可。 +下载Github Actions自动编译的 app.js 直接运行即可。 ## 鸣谢 diff --git a/build/index.js b/build/index.js index 55e83df20..9bdd53a1a 100644 --- a/build/index.js +++ b/build/index.js @@ -66,7 +66,7 @@ async function build(type) { console.error(`Module pack failed: ${m}`); } } - const f = fs.readFileSync(root('.build/development.js')).toString(); + const f = fs.readFileSync(root('.build/app.js')).toString(); const installer = fs.readFileSync(root('tool/install.js')).toString(); const file = `global._hydroModule=${JSON.stringify(j)};${f}`; fs.writeFileSync(root('.build/full.js'), file); diff --git a/build/webpack.js b/build/webpack.js index 684f1b1c7..1bfe8f318 100644 --- a/build/webpack.js +++ b/build/webpack.js @@ -22,7 +22,7 @@ const build = async (type) => { const config = { mode: type, entry: { - development: root('hydro/development.js'), + app: root('hydro/loader.js'), }, output: { filename: '[name].js', diff --git a/hydro/development.js b/hydro/development.js deleted file mode 100644 index 814409941..000000000 --- a/hydro/development.js +++ /dev/null @@ -1,26 +0,0 @@ -process.stdin.setEncoding('utf8'); -process.stdin.on('data', async (input) => { - try { - const t = eval(input.toString().trim()); // eslint-disable-line no-eval - if (t instanceof Promise) console.log(await t); - else console.log(t); - } catch (e) { - console.warn(e); - } -}); -process.on('unhandledRejection', (e) => console.log(e)); - -global.onDestory = []; -async function terminate() { - for (const task of global.onDestory) { - // eslint-disable-next-line no-await-in-loop - await task(); - } - process.exit(0); -} -process.on('SIGINT', terminate); - -require('./loader').load().catch((e) => { - console.error(e); - process.exit(1); -}); diff --git a/hydro/loader.js b/hydro/loader.js index d024bbd43..acfd5d987 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -5,6 +5,8 @@ const fs = require('fs'); const os = require('os'); const zlib = require('zlib'); const path = require('path'); +const cluster = require('cluster'); +const numCPUs = require('os').cpus().length; const yaml = require('js-yaml'); function ensureDir(dir) { @@ -31,12 +33,9 @@ if (global._hydroModule) { } } -async function preload() { +async function unzip() { for (const i of pending) { try { - if (i.os) { - if (!i.os.includes(os.platform().toLowerCase())) throw new Error('Unsupported OS'); - } if (i.file) { i.files = {}; ensureDir(path.resolve(os.tmpdir(), 'hydro', i.id)); @@ -59,6 +58,21 @@ async function preload() { } } +async function preload() { + for (const i of pending) { + try { + if (i.os) { + if (!i.os.includes(os.platform().toLowerCase())) throw new Error('Unsupported OS'); + } + } catch (e) { + i.fail = true; + fail.push(i.id); + console.error(`Module Load Fail: ${i.id}`); + console.error(e); + } + } +} + async function handler() { for (const i of pending) { if (i.handler && !i.fail) { @@ -182,23 +196,33 @@ async function install() { await setup.setup(); } -async function load() { +const builtinLib = [ + 'axios', 'download', 'i18n', 'mail', 'markdown', + 'md5', 'misc', 'paginate', 'hash.hydro', 'rank', + 'template', 'validator', 'nav', 'sysinfo', +]; + +const builtinModel = [ + 'builtin', 'document', 'domain', 'blacklist', 'opcount', + 'setting', 'token', 'user', 'problem', 'record', + 'contest', 'message', 'solution', 'training', 'file', + 'discussion', 'system', +]; + +const builtinHandler = [ + 'home', 'problem', 'record', 'judge', 'user', + 'contest', 'training', 'discussion', 'manage', 'import', + 'misc', 'homework', 'domain', +]; + +const builtinScript = [ + 'install', 'uninstall', 'rating', 'recalcRating', +]; + +async function loadAsMaster() { ensureDir(path.resolve(os.tmpdir(), 'hydro')); - global.Hydro = { - handler: {}, - service: {}, - model: {}, - script: {}, - lib: {}, - nodeModules: { - bson: require('bson'), - 'js-yaml': require('js-yaml'), - mongodb: require('mongodb'), - }, - template: {}, - ui: {}, - }; pending.push(...await require('./lib/hpm').getInstalled()); + await unzip(); await preload(); require('./lib/i18n'); require('./utils'); @@ -221,11 +245,6 @@ async function load() { bus.subscribe(['system_database_connected'], h); require('./service/db'); }); - const builtinLib = [ - 'axios', 'download', 'i18n', 'mail', 'markdown', - 'md5', 'misc', 'paginate', 'hash.hydro', 'rank', - 'template', 'validator', 'nav', 'sysinfo', - ]; for (const i of builtinLib) require(`./lib/${i}`); await lib(); require('./service/gridfs'); @@ -233,12 +252,6 @@ async function load() { const server = require('./service/server'); await server.prepare(); await service(); - const builtinModel = [ - 'builtin', 'document', 'domain', 'blacklist', 'opcount', - 'setting', 'token', 'user', 'problem', 'record', - 'contest', 'message', 'solution', 'training', 'file', - 'discussion', - ]; for (const i of builtinModel) { const m = require(`./model/${i}`); if (m.ensureIndexes) await m.ensureIndexes(); @@ -247,13 +260,8 @@ async function load() { const dbVer = await system.get('db.ver'); if (dbVer !== 1) { const ins = require('./script/install'); - await ins.run('Root', 'rootroot'); + await ins.run({ username: 'Root', password: 'rootroot' }); } - const builtinHandler = [ - 'home', 'problem', 'record', 'judge', 'user', - 'contest', 'training', 'discussion', 'manage', 'import', - 'misc', 'homework', 'domain', - ]; for (const i of builtinHandler) require(`./handler/${i}`); await model(); await handler(); @@ -271,15 +279,129 @@ async function load() { } } } - const builtinScript = [ - 'install', 'uninstall', 'rating', 'recalcRating', - ]; + for (const i of builtinScript) require(`./script/${i}`); + await script(); + pending = []; + await server.start(await system.get('server.port')); +} + +async function loadAsWorker() { + pending.push(...await require('./lib/hpm').getInstalled()); + await preload(); + require('./lib/i18n'); + require('./utils'); + require('./error'); + require('./permission'); + require('./options'); + await Promise.all([locale(), template()]); + const bus = require('./service/bus'); + await new Promise((resolve) => { + const h = () => { + console.log('Database connected'); + bus.unsubscribe(['system_database_connected'], h); + resolve(); + }; + bus.subscribe(['system_database_connected'], h); + require('./service/db'); + }); + for (const i of builtinLib) require(`./lib/${i}`); + await lib(); + require('./service/gridfs'); + const server = require('./service/server'); + await server.prepare(); + await service(); + for (const i of builtinModel) require(`./model/${i}`); + for (const i of builtinHandler) require(`./handler/${i}`); + await model(); + await handler(); + for (const i in global.Hydro.handler) { + await global.Hydro.handler[i](); + } + const notfound = require('./handler/notfound'); + await notfound(); + for (const i in global.Hydro.service) { + if (global.Hydro.service[i].postInit) { + try { + await global.Hydro.service[i].postInit(); + } catch (e) { + console.error(e); + } + } + } for (const i of builtinScript) require(`./script/${i}`); await script(); pending = []; await server.start(); } +async function terminate() { + for (const task of global.onDestory) { + // eslint-disable-next-line no-await-in-loop + await task(); + } + process.exit(0); +} + +async function load() { + global.Hydro = { + handler: {}, + service: {}, + model: {}, + script: {}, + lib: {}, + nodeModules: { + bson: require('bson'), + 'js-yaml': require('js-yaml'), + mongodb: require('mongodb'), + }, + template: {}, + ui: {}, + }; + global.onDestory = []; + if (cluster.isMaster) { + console.log(`Master ${process.pid} Starting`); + process.stdin.setEncoding('utf8'); + process.stdin.on('data', async (input) => { + try { + const t = eval(input.toString().trim()); // eslint-disable-line no-eval + if (t instanceof Promise) console.log(await t); + else console.log(t); + } catch (e) { + console.warn(e); + } + }); + process.on('unhandledRejection', (e) => console.log(e)); + process.on('SIGINT', terminate); + await loadAsMaster(); + cluster.on('exit', (worker, code, signal) => { + console.log(`Worker ${worker.process.pid} exit: ${code} ${signal}`); + }); + cluster.on('disconnect', (worker) => { + console.log(`Worker ${worker.process.pid} disconnected`); + }); + cluster.on('listening', (worker, address) => { + console.log(`Worker ${worker.process.pid} listening at `, address); + }); + cluster.on('online', (worker) => { + console.log(`Worker ${worker.process.pid} is online`); + }); + for (let i = 0; i < numCPUs; i++) { + cluster.fork(); + } + } else { + console.log(`Worker ${process.pid} Starting`); + await loadAsWorker(); + console.log(`Worker ${process.pid} Started`); + } +} + module.exports = { load, pending, active, fail, }; + +if (!module.parent) { + load().catch((e) => { + console.error(e); + process.exit(1); + }); +} diff --git a/hydro/service/server.js b/hydro/service/server.js index cf1d65311..b41235562 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -438,12 +438,12 @@ function Validate(key, func) { else validate[key] = [func]; } -async function start() { +async function start(port) { app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); app.use(router.routes()).use(router.allowedMethods()); Route('*', Handler); - server.listen(await system.get('server.port')); - console.log('Server listening at: %s', await system.get('server.port')); + server.listen(port); + if (port) console.log('Server listening at: %s', port); } global.Hydro.service.server = module.exports = {