Merge remote-tracking branch 'origin/v0.10'
[platform/upstream/nodejs.git] / lib / domain.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 var util = require('util');
23 var EventEmitter = require('events');
24 var inherits = util.inherits;
25
26 // communicate with events module, but don't require that
27 // module to have to load this one, since this module has
28 // a few side effects.
29 EventEmitter.usingDomains = true;
30
31 // overwrite process.domain with a getter/setter that will allow for more
32 // effective optimizations
33 var _domain = [null];
34 Object.defineProperty(process, 'domain', {
35   enumerable: true,
36   get: function() {
37     return _domain[0];
38   },
39   set: function(arg) {
40     return _domain[0] = arg;
41   }
42 });
43
44 // objects with external array data are excellent ways to communicate state
45 // between js and c++ w/o much overhead
46 var _domain_flag = {};
47
48 // let the process know we're using domains
49 process._setupDomainUse(_domain, _domain_flag);
50
51 exports.Domain = Domain;
52
53 exports.create = exports.createDomain = function() {
54   return new Domain();
55 };
56
57 // it's possible to enter one domain while already inside
58 // another one.  the stack is each entered domain.
59 var stack = [];
60 exports._stack = stack;
61 // the active domain is always the one that we're currently in.
62 exports.active = null;
63
64
65 inherits(Domain, EventEmitter);
66
67 function Domain() {
68   EventEmitter.call(this);
69
70   this.members = [];
71 }
72
73 Domain.prototype.members = undefined;
74 Domain.prototype._disposed = undefined;
75
76
77 // Called by process._fatalException in case an error was thrown.
78 Domain.prototype._errorHandler = function errorHandler(er) {
79   var caught = false;
80   // ignore errors on disposed domains.
81   //
82   // XXX This is a bit stupid.  We should probably get rid of
83   // domain.dispose() altogether.  It's almost always a terrible
84   // idea.  --isaacs
85   if (this._disposed)
86     return true;
87
88   er.domain = this;
89   er.domainThrown = true;
90   // wrap this in a try/catch so we don't get infinite throwing
91   try {
92     // One of three things will happen here.
93     //
94     // 1. There is a handler, caught = true
95     // 2. There is no handler, caught = false
96     // 3. It throws, caught = false
97     //
98     // If caught is false after this, then there's no need to exit()
99     // the domain, because we're going to crash the process anyway.
100     caught = this.emit('error', er);
101
102     // Exit all domains on the stack.  Uncaught exceptions end the
103     // current tick and no domains should be left on the stack
104     // between ticks.
105     stack.length = 0;
106     exports.active = process.domain = null;
107   } catch (er2) {
108     // The domain error handler threw!  oh no!
109     // See if another domain can catch THIS error,
110     // or else crash on the original one.
111     // If the user already exited it, then don't double-exit.
112     if (this === exports.active) {
113       stack.pop();
114     }
115     if (stack.length) {
116       exports.active = process.domain = stack[stack.length - 1];
117       caught = process._fatalException(er2);
118     } else {
119       caught = false;
120     }
121     return caught;
122   }
123   return caught;
124 };
125
126
127 Domain.prototype.enter = function() {
128   if (this._disposed) return;
129
130   // note that this might be a no-op, but we still need
131   // to push it onto the stack so that we can pop it later.
132   exports.active = process.domain = this;
133   stack.push(this);
134   _domain_flag[0] = stack.length;
135 };
136
137
138 Domain.prototype.exit = function() {
139   // skip disposed domains, as usual, but also don't do anything if this
140   // domain is not on the stack.
141   var index = stack.lastIndexOf(this);
142   if (this._disposed || index === -1) return;
143
144   // exit all domains until this one.
145   stack.splice(index);
146   _domain_flag[0] = stack.length;
147
148   exports.active = stack[stack.length - 1];
149   process.domain = exports.active;
150 };
151
152
153 // note: this works for timers as well.
154 Domain.prototype.add = function(ee) {
155   // If the domain is disposed or already added, then nothing left to do.
156   if (this._disposed || ee.domain === this)
157     return;
158
159   // has a domain already - remove it first.
160   if (ee.domain)
161     ee.domain.remove(ee);
162
163   // check for circular Domain->Domain links.
164   // This causes bad insanity!
165   //
166   // For example:
167   // var d = domain.create();
168   // var e = domain.create();
169   // d.add(e);
170   // e.add(d);
171   // e.emit('error', er); // RangeError, stack overflow!
172   if (this.domain && (ee instanceof Domain)) {
173     for (var d = this.domain; d; d = d.domain) {
174       if (ee === d) return;
175     }
176   }
177
178   ee.domain = this;
179   this.members.push(ee);
180 };
181
182
183 Domain.prototype.remove = function(ee) {
184   ee.domain = null;
185   var index = this.members.indexOf(ee);
186   if (index !== -1)
187     this.members.splice(index, 1);
188 };
189
190
191 Domain.prototype.run = function(fn) {
192   if (this._disposed)
193     return;
194   this.enter();
195   var ret = fn.call(this);
196   this.exit();
197   return ret;
198 };
199
200
201 function intercepted(_this, self, cb, fnargs) {
202   if (self._disposed)
203     return;
204
205   if (fnargs[0] && fnargs[0] instanceof Error) {
206     var er = fnargs[0];
207     util._extend(er, {
208       domainBound: cb,
209       domainThrown: false,
210       domain: self
211     });
212     self.emit('error', er);
213     return;
214   }
215
216   var len = fnargs.length;
217   var args = [];
218   var i, ret;
219
220   self.enter();
221   if (fnargs.length > 1) {
222     for (i = 1; i < fnargs.length; i++)
223       args.push(fnargs[i]);
224     ret = cb.apply(_this, args);
225   } else {
226     ret = cb.call(_this);
227   }
228   self.exit();
229
230   return ret;
231 }
232
233
234 Domain.prototype.intercept = function(cb) {
235   var self = this;
236
237   function runIntercepted() {
238     return intercepted(this, self, cb, arguments);
239   }
240
241   return runIntercepted;
242 };
243
244
245 function bound(_this, self, cb, fnargs) {
246   if (self._disposed)
247     return;
248
249   var len = fnargs.length;
250   var args = [];
251   var i, ret;
252
253   self.enter();
254   if (fnargs.length > 0)
255     ret = cb.apply(_this, fnargs);
256   else
257     ret = cb.call(_this);
258   self.exit();
259
260   return ret;
261 }
262
263
264 Domain.prototype.bind = function(cb) {
265   var self = this;
266
267   function runBound() {
268     return bound(this, self, cb, arguments);
269   }
270
271   runBound.domain = this;
272
273   return runBound;
274 };
275
276
277 Domain.prototype.dispose = util.deprecate(function() {
278   if (this._disposed) return;
279
280   // if we're the active domain, then get out now.
281   this.exit();
282
283   // remove from parent domain, if there is one.
284   if (this.domain) this.domain.remove(this);
285
286   // kill the references so that they can be properly gc'ed.
287   this.members.length = 0;
288
289   // mark this domain as 'no longer relevant'
290   // so that it can't be entered or activated.
291   this._disposed = true;
292 });