Skip to content

Instantly share code, notes, and snippets.

@isaacs
Last active December 15, 2015 13:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isaacs/5264897 to your computer and use it in GitHub Desktop.
Save isaacs/5264897 to your computer and use it in GitHub Desktop.
From 5ae26f3750f6ecc0fb284c5ffe0f680c5d053530 Mon Sep 17 00:00:00 2001
From: isaacs <i@izs.me>
Date: Wed, 27 Mar 2013 18:41:41 -0700
Subject: [PATCH] doc: Add 'don't ignore errors' section to domain
Also, an example program of using cluster and domain to handle errors
safely, with zero downtime, using process isolation.
---
doc/api/domain.markdown | 170 ++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 153 insertions(+), 17 deletions(-)
diff --git a/doc/api/domain.markdown b/doc/api/domain.markdown
index d0a6c87..4a0ee67 100644
--- a/doc/api/domain.markdown
+++ b/doc/api/domain.markdown
@@ -7,17 +7,159 @@ single group. If any of the event emitters or callbacks registered to a
domain emit an `error` event, or throw an error, then the domain object
will be notified, rather than losing the context of the error in the
`process.on('uncaughtException')` handler, or causing the program to
-exit with an error code.
+exit immediately with an error code.
-This feature is new in Node version 0.8. It is a first pass, and is
-expected to change significantly in future versions. Please use it and
-provide feedback.
+## Warning: Don't Ignore Errors!
-Due to their experimental nature, the Domains features are disabled unless
-the `domain` module is loaded at least once. No domains are created or
-registered by default. This is by design, to prevent adverse effects on
-current programs. It is expected to be enabled by default in future
-Node.js versions.
+<!-- type=misc -->
+
+Domain error handlers are not a substitute for closing down your
+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 you 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, you might have many
+connections open, and it is not reasonable to abruptly shut those down
+because an error was triggered by someone else.
+
+The better approach is 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 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:
+
+```javascript
+// XXX WARNING! BAD IDEA!
+
+var d = require('domain').create();
+d.on('error', function(er) {
+ // The error won't crash the process, but what it does is worse!
+ // Though we've prevented abrupt process restarting, we are leaking
+ // resources like crazy if this ever happens.
+ // This is no better than process.on('uncaughtException')!
+ console.log('error, but oh well', er.message);
+});
+require('http').createServer(function(req, res) {
+ handleRequest(req, res);
+}).listen(PORT);
+```
+
+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.
+
+```javascript
+// Much better!
+
+var cluster = require('cluster');
+var PORT = +process.env.PORT || 1337;
+
+if (cluster.isMaster) {
+ // In real life, you'd probably use more than just 2 workers,
+ // and perhaps not put the master and worker in the same file.
+ //
+ // You can also of course get a bit fancier about logging, and
+ // implement whatever custom logic you need to prevent DoS
+ // attacks and other bad behavior.
+ //
+ // See the options in the cluster documentation.
+ //
+ // The important thing is that the master does very little,
+ // increasing our resilience to unexpected errors.
+
+ cluster.fork();
+ cluster.fork();
+
+ cluster.on('disconnect', function(worker) {
+ console.error('disconnect!');
+ cluster.fork();
+ });
+
+} else {
+ // the worker
+ //
+ // This is where we put our bugs!
+
+ var domain = require('domain');
+
+ // See the cluster documentation for more details about using
+ // worker processes to serve requests. How it works, caveats, etc.
+
+ var server = require('http').createServer(function(req, res) {
+ var d = domain.create();
+ d.on('error', function(er) {
+ console.error('error', er.stack);
+
+ // Note: we're in dangerous territory!
+ // By definition, something unexpected occurred,
+ // which we probably didn't want.
+ // Anything can happen now! Be very careful!
+
+ try {
+ // make sure we close down within 30 seconds
+ var killtimer = setTimeout(function() {
+ process.exit(1);
+ }, 30000);
+ // But don't keep the process open just for that!
+ killtimer.unref();
+
+ // stop taking new requests.
+ server.close();
+
+ // Let the master know we're dead. This will trigger a
+ // 'disconnect' in the cluster master, and then it will fork
+ // a new worker.
+ cluster.worker.disconnect();
+
+ // try to send an error to the request that triggered the problem
+ res.statusCode = 500;
+ res.setHeader('content-type', 'text/plain');
+ res.end('Oops, there was a problem!\n');
+ } catch (er2) {
+ // oh well, not much we can do at this point.
+ console.error('Error sending 500!', er2.stack);
+ }
+ });
+
+ // Because req and res were created before this domain existed,
+ // we need to explicitly add them.
+ // See the explanation of implicit vs explicit binding below.
+ d.add(req);
+ d.add(res);
+
+ // Now run the handler function in the domain.
+ d.run(function() {
+ handleRequest(req, res);
+ });
+ });
+ server.listen(PORT);
+}
+
+// This part isn't important. Just an example routing thing.
+// You'd put your fancy application logic here.
+function handleRequest(req, res) {
+ switch(req.url) {
+ case '/error':
+ // We do some async stuff, and then...
+ setTimeout(function() {
+ // Whoops!
+ flerb.bark();
+ });
+ break;
+ default:
+ res.end('ok');
+ }
+}
+```
## Additions to Error objects
@@ -38,7 +180,7 @@ are added to it.
<!--type=misc-->
-If domains are in use, then all new EventEmitter objects (including
+If domains are in use, then all **new** EventEmitter objects (including
Stream objects, requests, responses, etc.) will be implicitly bound to
the active domain at the time of their creation.
@@ -53,7 +195,7 @@ were, then it would be too easy to prevent request and response objects
from being properly garbage collected.
If you *want* to nest Domain objects as children of a parent Domain,
-then you must explicitly add them, and then dispose of them later.
+then you must explicitly add them.
Implicit binding routes thrown errors and `'error'` events to the
Domain's `error` event, but does not register the EventEmitter on the
@@ -94,14 +236,8 @@ serverDomain.run(function() {
try {
res.writeHead(500);
res.end('Error occurred, sorry.');
- res.on('close', function() {
- // forcibly shut down any other things added to this domain
- reqd.dispose();
- });
} catch (er) {
console.error('Error sending 500', er, req.url);
- // tried our best. clean up anything remaining.
- reqd.dispose();
}
});
}).listen(1337);
--
1.7.5.4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment