refactor: process and signals

This commit is contained in:
cha0s 2024-02-06 12:31:47 -06:00
parent 94a606c5d1
commit 08898a5e19
9 changed files with 80 additions and 29 deletions

View File

@ -14,7 +14,7 @@
"dox:serve": "flecks dox docusaurus && cd website && DOCUSAURUS_GENERATED_FILES_DIR_NAME=node_modules/.cache/docusaurus node_modules/.bin/docusaurus build --no-minify --out-dir ../dox-tmp && node_modules/.bin/docusaurus serve --dir ../dox-tmp", "dox:serve": "flecks dox docusaurus && cd website && DOCUSAURUS_GENERATED_FILES_DIR_NAME=node_modules/.cache/docusaurus node_modules/.bin/docusaurus build --no-minify --out-dir ../dox-tmp && node_modules/.bin/docusaurus serve --dir ../dox-tmp",
"dox": "flecks dox docusaurus && cd website && DOCUSAURUS_GENERATED_FILES_DIR_NAME=node_modules/.cache/docusaurus node_modules/.bin/docusaurus", "dox": "flecks dox docusaurus && cd website && DOCUSAURUS_GENERATED_FILES_DIR_NAME=node_modules/.cache/docusaurus node_modules/.bin/docusaurus",
"lint": "node build/tasks lint", "lint": "node build/tasks lint",
"test": "node build/tasks -- test -t 60000" "test": "node build/tasks -- test -t 300000"
}, },
"devDependencies": { "devDependencies": {
"husky": "^9.0.7", "husky": "^9.0.7",

View File

@ -18,6 +18,7 @@ const {
const D = require('@flecks/core/build/debug'); const D = require('@flecks/core/build/debug');
const { const {
add, add,
binaryPath,
lockFile, lockFile,
spawnWith, spawnWith,
} = require('@flecks/core/src/server'); } = require('@flecks/core/src/server');
@ -178,7 +179,7 @@ exports.commands = (program, flecks) => {
debug('Building...', opts); debug('Building...', opts);
const webpackConfig = await flecks.resolveBuildConfig('fleckspack.config.js'); const webpackConfig = await flecks.resolveBuildConfig('fleckspack.config.js');
const cmd = [ const cmd = [
'npx', 'webpack', await binaryPath('webpack'),
...((watch || hot) ? ['watch'] : []), ...((watch || hot) ? ['watch'] : []),
'--config', webpackConfig, '--config', webpackConfig,
'--mode', (production && !hot) ? 'production' : 'development', '--mode', (production && !hot) ? 'production' : 'development',
@ -210,7 +211,7 @@ exports.commands = (program, flecks) => {
.map((pkg) => join(process.cwd(), pkg)) .map((pkg) => join(process.cwd(), pkg))
.map(async (cwd) => { .map(async (cwd) => {
const cmd = [ const cmd = [
'npx', 'eslint', await binaryPath('eslint'),
'--config', await flecks.resolveBuildConfig('eslint.config.js'), '--config', await flecks.resolveBuildConfig('eslint.config.js'),
'.', '.',
]; ];

View File

@ -1,10 +1,22 @@
const {spawn} = require('child_process'); const {exec, spawn} = require('child_process');
const D = require('../../build/debug'); const D = require('../../build/debug');
const debug = D('@flecks/core/server'); const debug = D('@flecks/core/server');
const debugSilly = debug.extend('silly'); const debugSilly = debug.extend('silly');
exports.binaryPath = (binary) => (
new Promise((resolve, reject) => {
exec(`npx which ${binary}`, (error, stdout) => {
if (error) {
reject(error);
return;
}
resolve(stdout.trim());
});
})
);
exports.processCode = (child) => new Promise((resolve, reject) => { exports.processCode = (child) => new Promise((resolve, reject) => {
child.on('error', reject); child.on('error', reject);
child.on('exit', (code) => { child.on('exit', (code) => {
@ -13,6 +25,8 @@ exports.processCode = (child) => new Promise((resolve, reject) => {
}); });
}); });
const children = [];
exports.spawnWith = (cmd, opts = {}) => { exports.spawnWith = (cmd, opts = {}) => {
debug("spawning: '%s'", cmd.join(' ')); debug("spawning: '%s'", cmd.join(' '));
debugSilly('with options: %O', opts); debugSilly('with options: %O', opts);
@ -24,5 +38,31 @@ exports.spawnWith = (cmd, opts = {}) => {
...opts.env, ...opts.env,
}, },
}); });
children.push(child);
child.on('exit', () => {
children.splice(children.indexOf(child), 1);
});
return child; return child;
}; };
let killed = false;
function handleTerminationEvent(signal) {
// Clean up on exit.
process.on(signal, () => {
if (killed) {
return;
}
killed = true;
children.forEach((child) => {
child.kill();
});
if ('exit' !== signal) {
process.exit(1);
}
});
}
handleTerminationEvent('exit');
handleTerminationEvent('SIGINT');
handleTerminationEvent('SIGTERM');

View File

@ -58,7 +58,7 @@ module.exports = async (env, argv, flecks) => {
NODE_PRESERVE_SYMLINKS: flecks.roots.some(([path, request]) => path !== request) ? 1 : 0, NODE_PRESERVE_SYMLINKS: flecks.roots.some(([path, request]) => path !== request) ? 1 : 0,
}, },
exec: 'index.js', exec: 'index.js',
killOnExit: !!hot, killOnExit: !hot,
// Bail hard on unhandled rejections and report. // Bail hard on unhandled rejections and report.
nodeArgs: [...nodeArgs, '--unhandled-rejections=strict', '--trace-uncaught'], nodeArgs: [...nodeArgs, '--unhandled-rejections=strict', '--trace-uncaught'],
// HMR. // HMR.

View File

@ -16,6 +16,12 @@ class StartServerPlugin {
signal: false, signal: false,
...('string' === typeof options ? {name: options} : options), ...('string' === typeof options ? {name: options} : options),
}; };
['exit', 'SIGINT', 'SIGTERM']
.forEach((event) => {
process.on(event, () => {
this.worker.kill('exit' === event ? 'SIGKILL' : event);
});
});
} }
apply(compiler) { apply(compiler) {
@ -85,23 +91,21 @@ class StartServerPlugin {
args, args,
...(inspectPort && {inspectPort}), ...(inspectPort && {inspectPort}),
}); });
const setupListeners = (worker) => { this.worker = cluster.fork(env);
if (killOnExit) { if (killOnExit) {
worker.on('exit', () => { this.worker.on('exit', (code) => {
process.exit(); process.exit(code);
}); });
} }
worker.on('message', (message) => { else {
if ('hmr-restart' === message) { this.worker.on('disconnect', () => {
if (this.worker.exitedAfterDisconnect) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('[HMR] Restarting application...'); console.error('[HMR] Restarting application...');
this.worker = cluster.fork(env); this.worker = cluster.fork(env);
setupListeners(this.worker);
} }
}); });
}; }
this.worker = cluster.fork(env);
setupListeners(this.worker);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.worker.on('error', reject); this.worker.on('error', reject);
this.worker.on('online', resolve); this.worker.on('online', resolve);

View File

@ -35,7 +35,7 @@ import {D, Flecks} from '@flecks/core';
if (module.hot) { if (module.hot) {
module.hot.accept('./runtime', () => { module.hot.accept('./runtime', () => {
if (cluster.isWorker) { if (cluster.isWorker) {
cluster.worker.send('hmr-restart'); cluster.worker.disconnect();
const error = new Error('Restart requested!'); const error = new Error('Restart requested!');
error.stack = ''; error.stack = '';
throw error; throw error;

View File

@ -2,7 +2,7 @@ import {cp, mkdir} from 'fs/promises';
import {join} from 'path'; import {join} from 'path';
import {rimraf} from '@flecks/build/server'; import {rimraf} from '@flecks/build/server';
import {processCode, spawnWith} from '@flecks/core/server'; import {binaryPath, processCode, spawnWith} from '@flecks/core/server';
import {listen} from './listen'; import {listen} from './listen';
@ -21,9 +21,9 @@ export async function createApplicationAt(path) {
return qualified; return qualified;
} }
export function build(path, {args = [], opts = {}} = {}) { export async function buildChild(path, {args = [], opts = {}} = {}) {
return processCode(spawnWith( return spawnWith(
['npx', 'flecks', 'build', ...args], [await binaryPath('flecks'), 'build', ...args],
{ {
...opts, ...opts,
env: { env: {
@ -33,7 +33,11 @@ export function build(path, {args = [], opts = {}} = {}) {
...opts.env, ...opts.env,
}, },
}, },
)); );
}
export async function build(path, {args = [], opts = {}} = {}) {
return processCode(await buildChild(path, {args, opts}));
} }
export async function serverActions(path, actions) { export async function serverActions(path, actions) {

View File

@ -1,3 +1,4 @@
import cluster from 'cluster';
import {createConnection} from 'net'; import {createConnection} from 'net';
const { const {
@ -18,7 +19,12 @@ export const hooks = {
if (!FLECKS_SERVER_TEST_SOCKET) { if (!FLECKS_SERVER_TEST_SOCKET) {
return; return;
} }
const socket = createConnection({path: FLECKS_SERVER_TEST_SOCKET}); const socket = createConnection(FLECKS_SERVER_TEST_SOCKET);
if (cluster.isWorker) {
cluster.worker.on('disconnect', () => {
socket.end();
});
}
flecks.socket = socket; flecks.socket = socket;
socket.on('connect', () => { socket.on('connect', () => {
socket.on('data', (data) => { socket.on('data', (data) => {

View File

@ -174,7 +174,7 @@ exports.hooks = {
'--hot', '--hot',
'--config', await flecks.resolveBuildConfig('fleckspack.config.js'), '--config', await flecks.resolveBuildConfig('fleckspack.config.js'),
]; ];
const child = spawnWith( spawnWith(
cmd, cmd,
{ {
env: { env: {
@ -182,10 +182,6 @@ exports.hooks = {
}, },
}, },
); );
// Clean up on exit.
process.on('exit', () => {
child.kill();
});
// Remove the build config since we're handing off to WDS. // Remove the build config since we're handing off to WDS.
delete configs.web; delete configs.web;
}, },