For my specific use case in a node app, I'd like jobs created as processes (lots of IO and long running). Experimenting with the examples, I've come up with the parent / child code as per below. The problem is I cannot get the exception handling to work properly without a "sleep" after it calls bree.stop(). With it, it works as expected. My naive understanding is that signal handlers should not be async in nature. Is there a better way? BTW, same problem exists if I use Graceful.
Main "dispatch" app
const path = require('path');
// optional
const ms = require('ms');
const dayjs = require('dayjs');
const Graceful = require('@ladjs/graceful');
const Cabin = require('cabin');
const later = require('@breejs/later');
// required
const Bree = require('bree');
const sleep = require('util').promisify(setTimeout);
process.on('SIGINT', () => {
myExit('SIGINT');
});
process.on('SIGQUIT', () => {
myExit('SIGQUIT');
});
process.on('SIGTERM', () => {
myExit('SIGTERM');
});
async function myExit(name) {
console.log(`\mBREE Parent Ending ${name}`);
bree.stop();
//removing sleep here now does not allow child to close properly (at least no logging is seen but process is closed)
await sleep(500);
process.exit(0);
}
console.log(`BREE starting with PID:${process.pid}`);
//
// NOTE: see the "Instance Options" section below in this README
// for the complete list of options and their defaults
//
const bree = new Bree({
//
// NOTE: by default the `logger` is set to `console`
// however we recommend you to use CabinJS as it
// will automatically add application and worker metadata
// to your log output, and also masks sensitive data for you
// <https://cabinjs.com>
//
// logger: new Cabin(),
//
// NOTE: instead of passing this Array as an option
// you can create a `./jobs/index.js` file, exporting
// this exact same array as `module.exports = [ ... ]`
// doing so will allow you to keep your job configuration and the jobs
// themselves all in the same folder and very organized
//
// See the "Job Options" section below in this README
// for the complete list of job options and configurations
//
jobs: [
// runs `./jobs/foo.js` on start
// 'job-async',
// 'job-async',
//'job-compute'
// runs `./jobs/worker-5.js` on after 10 minutes have elapsed
{
name: 'job-async-copy',
interval: 5000,//later.parse.text('every 5 seconds'),
worker: {
workerData: { name: 'joe' }
}
},
]
});
// handle graceful reloads, pm2 support, and events like SIGHUP, SIGINT, etc.
// const graceful = new Graceful({ brees: [bree] });
// graceful.listen();
// start all jobs (this is the equivalent of reloading a crontab):
bree.start();
//bree.stop('job-async');
/*
// start only a specific job:
bree.start('foo');
// stop all jobs
bree.stop();
// stop only a specific job:
bree.stop('beep');
// run all jobs (this does not abide by timeout/interval/cron and spawns workers immediately)
bree.run();
// run a specific job (...)
bree.run('beep');
// add a job array after initialization:
bree.add(['boop']);
// this must then be started using one of the above methods
// add a job after initialization:
bree.add('boop');
// this must then be started using one of the above methods
// remove a job after initialization:
bree.remove('boop');
*/
bree.on('worker deleted', (name) => {
console.log('worker deleted', name);
// if (name === 'job-async' && !signal)
// bree.run(name);
});
bree.on('worker created', (name) => {
console.log(`new worker ${name}`);
});
Job
const threads = require('bthreads');
const sleep = require('util').promisify(setTimeout);
let signal = false;
process.on('SIGINT', () => {
myExit('SIGINT');
});
process.on('SIGQUIT', () => {
myExit('SIGQUIT');
});
process.on('SIGTERM', () => {
myExit('SIGTERM');
});
function myExit(name) {
console.log(`ASYNC received ${name}`);
signal = true;
// if (threads.parentPort) {
// threads.parentPort.postMessage('cancel');
// console.log(`Cancel sent`);
// } else
// console.log(`Exiting...`);
}
if (!threads.isMainThread) {
threads.parentPort.on('message', async (message) => {
console.log(`Received message from parent [${message}]`);
// await sleep(300);
if (message === 'cancel') {
signal = true;
}
});
}
if (threads.isMainThread) {
console.log(`ASYNC on main threads`);
} else {
console.log(`ASYNC on worker thread`);
}
// if (threads.parentPort) {
// console.log(`Adding .... event receiver`);
// threads.parentPort.once('message', async (message) => {
// });
// }
let a = 1;
let name = 'll'; //threads.hasOwnProperty('workerData.name') ? "UNKNOWN" : threads.workerData.name
console.log(`async-COPY starting with PID:${process.pid} and name ${name}`);
async function x() {
for (;;) {
a++;
await sleep(300);
console.log(`COPY loop ${a}`);
// if (a==5)
// threads.parentPort.postMessage('error');
if (a>10 || signal) {
// if (threads.parentPort) {
// threads.parentPort.postMessage('COPY exit');
// }
if (signal) {
console.log(`COPY Exiting due to signal...`);
if (!threads.isMainThread) {
console.log(`Sending to Parent...`);
threads.parentPort.postMessage('cancelled');
}
}
process.exit(0);
}
}
}
x();