Skip to content

Commit 963459d

Browse files
committed
Domain feature
This is a squashed commit of the main work done on the domains-wip branch. The original commit messages are preserved for posterity: * Implicitly add EventEmitters to active domain * Implicitly add timers to active domain * domain: add members, remove ctor cb * Don't hijack bound callbacks for Domain error events * Add dispose method * Add domain.remove(ee) method * A test of multiple domains in process at once * Put the active domain on the process object * Only intercept error arg if explicitly requested * Typo * Don't auto-add new domains to the current domain While an automatic parent/child relationship is sort of neat, and leads to some nice error-bubbling characteristics, it also results in keeping a reference to every EE and timer created, unless domains are explicitly disposed of. * Explicitly adding one domain to another is still fine, of course. * Don't allow circular domain->domain memberships * Disposing of a domain removes it from its parent * Domain disposal turns functions into no-ops * More documentation of domains * More thorough dispose() semantics * An example using domains in an HTTP server * Don't handle errors on a disposed domain * Need to push, even if the same domain is entered multiple times * Array.push is too slow for the EE Ctor * lint domain * domain: docs * Also call abort and destroySoon to clean up event emitters * domain: Wrap destroy methods in a try/catch * Attach tick callbacks to active domain * domain: Only implicitly bind timers, not explicitly * domain: Don't fire timers when disposed. * domain: Simplify naming so that MakeCallback works on Timers * Add setInterval and nextTick to domain test * domain: Make stack private
1 parent a26bee8 commit 963459d

11 files changed

Lines changed: 879 additions & 3 deletions

File tree

doc/api/_toc.markdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* [Process](process.html)
99
* [Utilities](util.html)
1010
* [Events](events.html)
11+
* [Domain](domain.html)
1112
* [Buffer](buffer.html)
1213
* [Stream](stream.html)
1314
* [Crypto](crypto.html)

doc/api/all.markdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
@include process
99
@include util
1010
@include events
11+
@include domain
1112
@include buffer
1213
@include stream
1314
@include crypto

doc/api/domain.markdown

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Domain
2+
3+
Stability: 1 - Experimental
4+
5+
Domains provide a way to handle multiple different IO operations as a
6+
single group. If any of the event emitters or callbacks registered to a
7+
domain emit an `error` event, or throw an error, then the domain object
8+
will be notified, rather than losing the context of the error in the
9+
`process.on('uncaughtException')` handler, or causing the program to
10+
exit with an error code.
11+
12+
This feature is new in Node version 0.8. It is a first pass, and is
13+
expected to change significantly in future versions. Please use it and
14+
provide feedback.
15+
16+
Due to their experimental nature, the Domains features are disabled unless
17+
the `domain` module is loaded at least once. No domains are created or
18+
registered by default. This is by design, to prevent adverse effects on
19+
current programs. It is expected to be enabled by default in future
20+
Node.js versions.
21+
22+
## Additions to Error objects
23+
24+
<!-- type=misc -->
25+
26+
Any time an Error object is routed through a domain, a few extra fields
27+
are added to it.
28+
29+
* `error.domain` The domain that first handled the error.
30+
* `error.domain_emitter` The event emitter that emitted an 'error' event
31+
with the error object.
32+
* `error.domain_bound` The callback function which was bound to the
33+
domain, and passed an error as its first argument.
34+
* `error.domain_thrown` A boolean indicating whether the error was
35+
thrown, emitted, or passed to a bound callback function.
36+
37+
## Implicit Binding
38+
39+
<!--type=misc-->
40+
41+
If domains are in use, then all new EventEmitter objects (including
42+
Stream objects, requests, responses, etc.) will be implicitly bound to
43+
the active domain at the time of their creation.
44+
45+
Additionally, callbacks passed to lowlevel event loop requests (such as
46+
to fs.open, or other callback-taking methods) will automatically be
47+
bound to the active domain. If they throw, then the domain will catch
48+
the error.
49+
50+
In order to prevent excessive memory usage, Domain objects themselves
51+
are not implicitly added as children of the active domain. If they
52+
were, then it would be too easy to prevent request and response objects
53+
from being properly garbage collected.
54+
55+
If you *want* to nest Domain objects as children of a parent Domain,
56+
then you must explicitly add them, and then dispose of them later.
57+
58+
Implicit binding routes thrown errors and `'error'` events to the
59+
Domain's `error` event, but does not register the EventEmitter on the
60+
Domain, so `domain.dispose()` will not shut down the EventEmitter.
61+
Implicit binding only takes care of thrown errors and `'error'` events.
62+
63+
## domain.create()
64+
65+
* return: {Domain}
66+
67+
Returns a new Domain object.
68+
69+
## Class: Domain
70+
71+
The Domain class encapsulates the functionality of routing errors and
72+
uncaught exceptions to the active Domain object.
73+
74+
Domain is a child class of EventEmitter. To handle the errors that it
75+
catches, listen to its `error` event.
76+
77+
### domain.members
78+
79+
* {Array}
80+
81+
An array of timers and event emitters that have been explicitly added
82+
to the domain.
83+
84+
### domain.add(emitter)
85+
86+
* `emitter` {EventEmitter | Timer} emitter or timer to be added to the domain
87+
88+
Explicitly adds an emitter to the domain. If any event handlers called by
89+
the emitter throw an error, or if the emitter emits an `error` event, it
90+
will be routed to the domain's `error` event, just like with implicit
91+
binding.
92+
93+
This also works with timers that are returned from `setInterval` and
94+
`setTimeout`. If their callback function throws, it will be caught by
95+
the domain 'error' handler.
96+
97+
If the Timer or EventEmitter was already bound to a domain, it is removed
98+
from that one, and bound to this one instead.
99+
100+
### domain.remove(emitter)
101+
102+
* `emitter` {EventEmitter | Timer} emitter or timer to be removed from the domain
103+
104+
The opposite of `domain.add(emitter)`. Removes domain handling from the
105+
specified emitter.
106+
107+
### domain.bind(cb)
108+
109+
* `cb` {Function} The callback function
110+
* return: {Function} The bound function
111+
112+
The returned function will be a wrapper around the supplied callback
113+
function. When the returned function is called, any errors that are
114+
thrown will be routed to the domain's `error` event.
115+
116+
#### Example
117+
118+
var d = domain.create();
119+
120+
function readSomeFile(filename, cb) {
121+
fs.readFile(filename, d.bind(function(er, data) {
122+
// if this throws, it will also be passed to the domain
123+
return cb(er, JSON.parse(data));
124+
}));
125+
}
126+
127+
d.on('error', function(er) {
128+
// an error occurred somewhere.
129+
// if we throw it now, it will crash the program
130+
// with the normal line number and stack message.
131+
});
132+
133+
### domain.intercept(cb)
134+
135+
* `cb` {Function} The callback function
136+
* return: {Function} The intercepted function
137+
138+
This method is almost identical to `domain.bind(cb)`. However, in
139+
addition to catching thrown errors, it will also intercept `Error`
140+
objects sent as the first argument to the function.
141+
142+
In this way, the common `if (er) return cb(er);` pattern can be replaced
143+
with a single error handler in a single place.
144+
145+
#### Example
146+
147+
var d = domain.create();
148+
149+
function readSomeFile(filename, cb) {
150+
fs.readFile(filename, d.intercept(function(er, data) {
151+
// if this throws, it will also be passed to the domain
152+
// additionally, we know that 'er' will always be null,
153+
// so the error-handling logic can be moved to the 'error'
154+
// event on the domain instead of being repeated throughout
155+
// the program.
156+
return cb(er, JSON.parse(data));
157+
}));
158+
}
159+
160+
d.on('error', function(er) {
161+
// an error occurred somewhere.
162+
// if we throw it now, it will crash the program
163+
// with the normal line number and stack message.
164+
});
165+
166+
### domain.dispose()
167+
168+
The dispose method destroys a domain, and makes a best effort attempt to
169+
clean up any and all IO that is associated with the domain. Streams are
170+
aborted, ended, closed, and/or destroyed. Timers are cleared.
171+
Explicitly bound callbacks are no longer called. Any error events that
172+
are raised as a result of this are ignored.
173+
174+
The intention of calling `dispose` is generally to prevent cascading
175+
errors when a critical part of the Domain context is found to be in an
176+
error state.
177+
178+
Note that IO might still be performed. However, to the highest degree
179+
possible, once a domain is disposed, further errors from the emitters in
180+
that set will be ignored. So, even if some remaining actions are still
181+
in flight, Node.js will not communicate further about them.

0 commit comments

Comments
 (0)