How to quickly get started with VUE3.0: Enter learning.
After our service is released, it will inevitably be scheduled by the running environment (such as containers, pm2, etc.), service upgrades will cause restarts, and various exceptions will cause the process to crash; in general, the running environment has Health monitoring of the service process will restart the process when the process is abnormal. When upgrading, there is also a rolling upgrade strategy. However, the scheduling strategy of the running environment treats our service process as a black box and does not care about the internal running conditions of the service process. Therefore, our service process needs to actively sense the scheduling actions of the running environment and then perform some exit cleanup actions. .
So today we will sort out the various situations that may cause the Node.js process to exit, and what we can do by listening to these process exit events.
Principle:
When a process wants to exit, there are nothing more than two situations. One is that the process actively exits, and the other is that it receives a system signal requiring the process to exit.
System signal notification exit
Common system signals are listed in the Node.js official document. We mainly focus on a few:
When receiving a non-forced exit signal, the Node.js process can listen to the exit signal and do some custom exit logic. For example, we wrote a cli tool that takes a long time to execute a task. If the user wants to exit the process through ctrl+c before the task is completed, the user can be prompted to wait:
const readline = require('readline'); process.on('SIGINT', () => { // We use readline to simply implement interaction in the command line const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('The task has not been completed yet, are you sure you want to exit?', answer => { if (answer === 'yes') { console.log('Task execution interrupted, exit process'); process.exit(0); } else { console.log('Task continues...'); } rl.close(); }); }); // Simulate a task that takes 1 minute to execute const longTimeTask = () => { console.log('task start...'); setTimeout(() => { console.log('task end'); }, 1000 * 60); }; longTimeTask();
The effect is as follows. Every time ctrl + c is pressed, the user will be prompted:
The process actively exits
Node.js. The process actively exits, mainly including the following situations:
We know that pm2 has the effect of a daemon process. In your case When your process exits with an error, pm2 will restart your process. We also implement the effect of a daemon child process in the cluster mode of Node.js (actually pm2 has similar logic):
const cluster = require('cluster' ); const http = require('http'); const numCPUs = require('os').cpus().length; const process = require('process'); //Main process code if (cluster.isMaster) { console.log(`Start the main process: ${process.pid}`); // Based on the number of cpu cores, create a working process for (let i = 0; i < numCPUs; i++) { cluster.fork(); } //Listen to the worker process exit event cluster.on('exit', (worker, code, signal) => { console.log(`Worker process ${worker.process.pid} exited, error code: ${code || signal}, restarting...`); //Restart the child process cluster.fork(); }); } // Worker process code if (cluster.isWorker) { // Listen for uncaught error events process.on('uncaughtException', error => { console.log(`An error occurred in worker process ${process.pid}`, error); process.emit('disconnect'); process.exit(1); }); //Create web server // Each worker process will listen to port 8000 (Node.js will handle it internally and will not cause port conflicts) http.createServer((req, res) => { res.writeHead(200); res.end('hello worldn'); }).listen(8000); console.log(`Start worker process: ${process.pid}`); }
Application Practice
The various situations in which the Node.js process exits have been analyzed above. Now we will make a tool to monitor the process exit. When the Node.js process exits, the user is allowed to execute its own exit logic:
// exit-hook. js //Save the exit tasks that need to be executed const tasks = []; //Add exit task const addExitTask = fn => tasks.push(fn); const handleExit = (code, error) => { // ...handleExit implementation is shown below}; //Listen to various exit events process.on('exit', code => handleExit(code)); // According to POSIX specifications, we use 128 + signal number to get the final exit code // Please refer to the picture below for the signal number. You can execute kill -l under the Linux system to view all signal numbers process.on('SIGHUP', () => handleExit(128 + 1)); process.on('SIGINT', () => handleExit(128 + 2)); process.on('SIGTERM', () => handleExit(128 + 15)); // Press ctrl+break to exit the signal process.on('SIGBREAK', () => handleExit(128 + 21)); // Exit code 1 represents an uncaught error that caused the process to exit process.on('uncaughtException', error => handleExit(1, error)); process.on('unhandledRejection', error => handleExit(1, error));
Signal number:
Next we need to implement the real process exit function handleExit, because the task function passed in by the user may be synchronous or asynchronous; we can use process.nextTick to ensure that the user's synchronization code has been executed, which can be easily understood process.nextTick will be executed after the synchronous code execution in each event loop stage is completed (understand process.nextTick); for asynchronous tasks, we need the user to call callback to tell us that the asynchronous task has been completed:
// Mark whether it is exiting, Avoid multiple executions of let isExiting = false; const handleExit = (code, error) => { if (isExiting) return; isExiting = true; // Mark that the exit action has been performed to avoid multiple calls to let hasDoExit = fasle; const doExit = () => { if (hasDoExit) return; hasDoExit = true process.nextTick(() => process.exit(code)) } // Record how many asynchronous tasks there are let asyncTaskCount = 0; // After the asynchronous task ends, the callback that the user needs to call let ayncTaskCallback = () => { process.nextTick(() => { asyncTaskCount-- if (asyncTaskCount === 0) doExit() }) } //Execute all exit tasks tasks.forEach(taskFn => { // If the number of parameters of the taskFn function is greater than 1, it is considered that the callback parameter is passed and it is an asynchronous task if (taskFn.length > 1) { asyncTaskCount++ taskFn(error, ayncTaskCallback) } else { taskFn(error) } }); // If there is an asynchronous task if (asyncTaskCount > 0) { // After more than 10s, force exit setTimeout(() => { doExit(); }, 10 * 1000) } else { doExit() } };
At this point, our process exit monitoring tool is completed. For the complete implementation, you can view this open source library async-exit-hook
https://github.com/darukjs/daruk-exit-hook.
The process gracefully exits
our web server. When restarting, being scheduled by a running container (pm2 or docker, etc.), or when an exception occurs and the process exits, we hope to perform exit actions, such as completing the response to requests connected to the service, cleaning up the database connection, printing error logs, triggering alarms, etc., do After completing the exit action, and then exit the process, we can use the process exit monitoring tool just now to implement:
const http = require('http'); //Create web server const server = http.createServer((req, res) => { res.writeHead(200); res.end('hello worldn'); }).listen(8000); // Use the tool we developed above to add a process exit task addExitTask((error, callback) => { // Print error logs, trigger alarms, release database connections, etc. console.log('Process exited abnormally', error) // Stop accepting new requests server.close((error) => { if (error) { console.log('Stop accepting new requests error', error) } else { console.log('Stop accepting new requests') } }) // A simpler approach is to wait for a certain period of time (here we wait 5s) for the existing requests to be completed // If you want to completely ensure that all requests are processed, you need to record each connection and wait until all connections are released. Execute the exit action // You can refer to the open source library https://github.com/sebhildebrandt/http-graceful-shutdown setTimout(callback, 5 * 1000) })
Summary
Through the above text, I believe you are already aware of the various situations that cause the Node.js process to exit. After the service is online, although tools such as k8s and pm2 can continuously pull up the process when the process exits abnormally to ensure the availability of the service, we should also actively sense the abnormality or scheduling of the process in the code, so as to be able to Catch problems earlier.