Domain error handlers are not a substitute for closing down a process when an error occurs.

    By the very nature of how [throw][] works in JavaScript, there is almost never any way to safely “pick up where it left off”, without leaking references, or creating some other sort of undefined brittle state.

    The safest way to respond to a thrown error is to shut down the process. Of course, in a normal web server, there may be many open connections, and it is not reasonable to abruptly shut those down because an error was triggered by someone else.

    The better approach is to send an error response to the request that triggered the error, while letting the others finish in their normal time, and stop listening for new requests in that worker.

    In this way, domain usage goes hand-in-hand with the cluster module, since the master process can fork a new worker when a worker encounters an error. For Node.js programs that scale to multiple machines, the terminating proxy or service registry can take note of the failure, and react accordingly.

    For example, this is not a good idea:

    1. // XXX WARNING! BAD IDEA!
    2. const d = require('domain').create();
    3. d.on('error', (er) => {
    4. // The error won't crash the process, but what it does is worse!
    5. // Though we've prevented abrupt process restarting, we are leaking
    6. // resources like crazy if this ever happens.
    7. // This is no better than process.on('uncaughtException')!
    8. console.log(`error, but oh well ${er.message}`);
    9. });
    10. d.run(() => {
    11. require('http').createServer((req, res) => {
    12. handleRequest(req, res);
    13. }).listen(PORT);
    14. });

    By using the context of a domain, and the resilience of separating our program into multiple worker processes, we can react more appropriately, and handle errors with much greater safety.

    1. // Much better!
    2. const cluster = require('cluster');
    3. const PORT = +process.env.PORT || 1337;
    4. if (cluster.isMaster) {
    5. // A more realistic scenario would have more than 2 workers,
    6. // and perhaps not put the master and worker in the same file.
    7. //
    8. // It is also possible to get a bit fancier about logging, and
    9. // implement whatever custom logic is needed to prevent DoS
    10. // attacks and other bad behavior.
    11. //
    12. // See the options in the cluster documentation.
    13. //
    14. // The important thing is that the master does very little,
    15. // increasing our resilience to unexpected errors.
    16. cluster.fork();
    17. cluster.fork();
    18. cluster.on('disconnect', (worker) => {
    19. console.error('disconnect!');
    20. cluster.fork();
    21. });
    22. } else {
    23. // the worker
    24. //
    25. // This is where we put our bugs!
    26. const domain = require('domain');
    27. // See the cluster documentation for more details about using
    28. // worker processes to serve requests. How it works, caveats, etc.
    29. const server = require('http').createServer((req, res) => {
    30. const d = domain.create();
    31. d.on('error', (er) => {
    32. console.error(`error ${er.stack}`);
    33. // We're in dangerous territory!
    34. // By definition, something unexpected occurred,
    35. // which we probably didn't want.
    36. // Anything can happen now! Be very careful!
    37. try {
    38. // Make sure we close down within 30 seconds
    39. const killtimer = setTimeout(() => {
    40. process.exit(1);
    41. }, 30000);
    42. // But don't keep the process open just for that!
    43. killtimer.unref();
    44. // Stop taking new requests.
    45. server.close();
    46. // Let the master know we're dead. This will trigger a
    47. // 'disconnect' in the cluster master, and then it will fork
    48. // a new worker.
    49. cluster.worker.disconnect();
    50. // Try to send an error to the request that triggered the problem
    51. res.statusCode = 500;
    52. res.setHeader('content-type', 'text/plain');
    53. res.end('Oops, there was a problem!\n');
    54. } catch (er2) {
    55. // Oh well, not much we can do at this point.
    56. console.error(`Error sending 500! ${er2.stack}`);
    57. }
    58. });
    59. // Because req and res were created before this domain existed,
    60. // we need to explicitly add them.
    61. // See the explanation of implicit vs explicit binding below.
    62. d.add(req);
    63. d.add(res);
    64. // Now run the handler function in the domain.
    65. d.run(() => {
    66. handleRequest(req, res);
    67. });
    68. });
    69. server.listen(PORT);
    70. }
    71. // This part is not important. Just an example routing thing.
    72. // Put fancy application logic here.
    73. function handleRequest(req, res) {
    74. switch (req.url) {
    75. case '/error':
    76. // We do some async stuff, and then...
    77. setTimeout(() => {
    78. // Whoops!
    79. flerb.bark();
    80. }, timeout);
    81. break;
    82. default:
    83. res.end('ok');
    84. }
    85. }