Exception and error handling in nodejs

Posted by depojones on Wed, 09 Feb 2022 14:29:16 +0100

Exception handling is a place that must be paid attention to in program operation. When an exception occurs, it should be paid attention to at the first time and solved quickly. Most programmers dare not guarantee that their code percentage is correct, so they should prevent and deal with exceptions in advance when writing code, try to give users a friendly prompt when exceptions occur, so as not to cause request timeout due to service suspension, and record and report the exception information for later troubleshooting and solution.

I Exception capture and handling of synchronization code

  1. Exceptions in synchronization code can be captured and handled by using try{}catch structure.

    try {
      throw new Error('error message');
    } catch (e) {
      console.error(e.message);
    }

    It can be captured normally.

II Error handling of asynchronous code

  1. try/catch interface

What is the effect of using try{}catch structure capture in asynchronous code?

try {
  setTimeout(()=>{
    throw new Error('error message');
  })
} catch (e) {
  console.error('error is:', e.message);
}

However, no asynchronous errors were caught.

  1. uncaughtException event of process

What about asynchronous errors? First of all, change your mind. Because exceptions are not prepared in advance and you can't control where they happen, you can stand a higher perspective, such as monitoring the error exceptions of the application process, so as to catch unexpected error exceptions and ensure that the application will not break down.

process.on('uncaughtException', (e)=>{
  console.error('process error is:', e.message);
});

The above code listens to the uncaughtException event from the process, which can capture the error information in the whole process including asynchrony, so as to ensure that the application does not break down.

However, new problems follow, because after the unexpected occurrence of the exception, when the exception occurs, it will be interrupted directly from the corresponding execution stack to the exception event captured by the process, which leads to the garbage collection function of the v8 engine not working according to the normal process, and then the memory leakage problem begins to appear.

Compared with exceptions, memory leakage is also a serious problem that cannot be ignored The on ('uncaughtexception ') approach is difficult to ensure that it will not cause memory leakage. Therefore, when an exception is caught, the process is explicitly killed manually and the node process is restarted, which not only ensures the release of memory, but also ensures the normal availability of subsequent services.

process.on('uncaughtException', (e)=>{
  console.error('process error is:', e.message);
  process.exit(1);
  restartServer(); // Restart service
});

However, the above method is a little direct, and we can't help wondering. What if the service can't be normally available during the period of restart after killing the process under the deployment of single process and single instance? This is obviously unreasonable.

  1. Using the domain module
    The domain module takes the operations that handle multiple different IO S as a group. Register events and call back to the domain. When an error event occurs or an error is thrown, the domain object will be notified. It will not lose the context, nor cause the program error to exit immediately, which is the same as process On ('uncaughtexception ') is different.

Domain modules can be divided into implicit binding and explicit binding:
Implicit binding: automatically bind the variables defined in the domain context to the domain object
Explicit binding: bind variables that are not defined in the domain context to the domain object in the form of code

const domain = require('domain');
const d = domain.create();

d.on('error', (err) => {
  console.log('err', err.message);
  console.log(needSend.message);
});

const needSend = { message: 'Some error information needs to be passed to the processor' };
d.add(needSend);

function excute() {
  try {
    setTimeout(()=>{
      throw new Error('error message');
    });
  } catch (e) {
    console.error('error is:', e.message);
  }
};

d.run(excute);

domin has obvious advantages. It can transfer some information when a problem occurs to the error handling function. It can do some processing work such as management and reporting. At least, it can ensure the service after restart. The program apes know what happened and can be checked by cable. They can also choose to transfer the context and do some follow-up processing. For example, when there is a service error, you can send the user request stack information to the downstream and inform the user of the service exception instead of waiting until the request automatically times out.

...
d.add(res);
...
d.on('error', (err) => {
  console.log('err', err.message);
  res.end('An exception occurred in the server. Please try again later!');
});

But it has nothing to do with process On ('uncaughtexception ') is the same. It is difficult to ensure that it will not cause memory leakage.

In addition, in the official documents, the domain module handles the obsolete state, but there is no other scheme to completely replace the domain module, but I can still use the version of node10. For the time being, I should not worry about the domain module being discarded.

III Multi process mode and restart after exception capture
The above method does not solve the problem perfectly. Think about how to make the exception not collapse after it occurs, not cause memory leakage after capturing the exception, and not cause service unavailability after restarting and releasing the cache?

A better solution is to deploy applications in the mode of multi process (cluster). When a process is caught by an exception, you can do some management and reporting, and then restart to release memory. At this time, after other requests are accepted, other processes can still provide services to the outside world. Of course, the premise is that your application can't be too numerous.

The following is the combination of cluster and domain to ensure the availability of services in a multi process way. At the same time, the error information can be passed down for reporting, and the context in which the error occurs can be reserved to return the request to the user without allowing the user to request timeout. Then, manually kill the abnormal process and restart.

const cluster = require('cluster');
const os = require('os');
const http = require('http');
const domain = require('domain');

const d = domain.create();

if (cluster.isMaster) {
  const cpuNum = os.cpus().length;
  for (let i = 0; i < cpuNum; ++i) {
    cluster.fork()
  };
  // fork work log
  cluster.on('fork', worker=>{
    console.info(`${new Date()} worker${worker.process.pid}The process started successfully`);
  });
  // Listen for exceptions, exit the process, and fork again
  cluster.on('exit',(worker,code,signal)=>{
    console.info(`${new Date()} worker${worker.process.pid}Process startup and abnormal exit`);
    cluster.fork();
  })
} else {
  http.createServer((req, res)=>{
    d.add(res);
    d.on('error', (err) => {
      console.log('Recorded err information', err.message);
      console.log('Wrong work id:', process.pid);
      // uploadError(err) / / report the error information to the monitor
      res.end('Server exception, Please try again later');
      // Kill the exception subprocess
      cluster.worker.kill(process.pid);
    });
    d.run(handle.bind(null, req, res));
  }).listen(8080);
}

function handle(req, res) {
  if (process.pid % 2 === 0) {
    throw new Error(`Error `);
  }
  res.end(`response by worker: ${process.pid}`);
};

Interested students strongly recommend copying the code locally and debugging and observation by themselves.

key word: Front end training

Topics: node.js