Skip to content

Commit ee8b68a

Browse files
author
Julien Gilli
committed
src: fix abort-on-uncaught-exception
This PR fixes 0af4c9e so that node aborts at the right time when throwing an error and using --abort-on-uncaught-exception. Basically, it wraps most node internal callbacks with: if (!domain || domain.emittingTopLevelError) runCallback(); else { try { runCallback(); } catch (err) { process._fatalException(err); } } so that V8 can abort properly in Isolate::Throw if --abort-on-uncaught-exception was passed on the command line, and domain can handle the error if one is active and not already in the top level domain's error handler. It also reverts 921f2de partially: node::FatalException does not abort anymore because at that time, it's already too late. It adds process._forceTickDone, which is really a hack to allow test-next-tick-error-spin.js to pass. It's here to basically avoid an infinite recursion when throwing in a domain from a nextTick callback, and queuing the same callback on the next tick from the domain's error handler. This change is an alternative approach to nodejs#3036 for fixing nodejs#3035. Fixes nodejs#3035.
1 parent 02448c6 commit ee8b68a

9 files changed

Lines changed: 504 additions & 108 deletions

lib/domain.js

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -71,39 +71,54 @@ Domain.prototype._errorHandler = function errorHandler(er) {
7171
er.domain = this;
7272
er.domainThrown = true;
7373
}
74-
// wrap this in a try/catch so we don't get infinite throwing
75-
try {
76-
// One of three things will happen here.
77-
//
78-
// 1. There is a handler, caught = true
79-
// 2. There is no handler, caught = false
80-
// 3. It throws, caught = false
81-
//
82-
// If caught is false after this, then there's no need to exit()
83-
// the domain, because we're going to crash the process anyway.
84-
caught = this.emit('error', er);
85-
86-
// Exit all domains on the stack. Uncaught exceptions end the
87-
// current tick and no domains should be left on the stack
88-
// between ticks.
89-
stack.length = 0;
90-
exports.active = process.domain = null;
91-
} catch (er2) {
92-
// The domain error handler threw! oh no!
93-
// See if another domain can catch THIS error,
94-
// or else crash on the original one.
95-
// If the user already exited it, then don't double-exit.
96-
if (this === exports.active) {
97-
stack.pop();
74+
75+
if (stack.length === 1) {
76+
try {
77+
this.emittingTopLevelError = true;
78+
79+
caught = this.emit('error', er);
80+
81+
stack.length = 0;
82+
exports.active = process.domain = null;
83+
} finally {
84+
this.emittingTopLevelError = false;
9885
}
99-
if (stack.length) {
100-
exports.active = process.domain = stack[stack.length - 1];
101-
caught = process._fatalException(er2);
102-
} else {
103-
caught = false;
86+
} else {
87+
// wrap this in a try/catch so we don't get infinite throwing
88+
try {
89+
// One of three things will happen here.
90+
//
91+
// 1. There is a handler, caught = true
92+
// 2. There is no handler, caught = false
93+
// 3. It throws, caught = false
94+
//
95+
// If caught is false after this, then there's no need to exit()
96+
// the domain, because we're going to crash the process anyway.
97+
caught = this.emit('error', er);
98+
99+
// Exit all domains on the stack. Uncaught exceptions end the
100+
// current tick and no domains should be left on the stack
101+
// between ticks.
102+
stack.length = 0;
103+
exports.active = process.domain = null;
104+
} catch (er2) {
105+
// The domain error handler threw! oh no!
106+
// See if another domain can catch THIS error,
107+
// or else crash on the original one.
108+
// If the user already exited it, then don't double-exit.
109+
if (this === exports.active) {
110+
stack.pop();
111+
}
112+
if (stack.length) {
113+
exports.active = process.domain = stack[stack.length - 1];
114+
caught = process._fatalException(er2);
115+
} else {
116+
caught = false;
117+
}
118+
return caught;
104119
}
105-
return caught;
106120
}
121+
107122
return caught;
108123
};
109124

@@ -176,20 +191,29 @@ Domain.prototype.run = function(fn) {
176191
if (this._disposed)
177192
return;
178193

179-
var ret;
194+
var ret, args;
180195

181196
this.enter();
182197
if (arguments.length >= 2) {
183198
var len = arguments.length;
184-
var args = new Array(len - 1);
199+
args = new Array(len - 1);
185200

186201
for (var i = 1; i < len; i++)
187202
args[i - 1] = arguments[i];
203+
}
188204

205+
// Wrap this in a try/catch block so that
206+
//
207+
// domain.run(function() { throw new Error("foo"); })
208+
//
209+
// actually emits an error event on the domain.
210+
try {
189211
ret = fn.apply(this, args);
190-
} else {
191-
ret = fn.call(this);
212+
} catch (err) {
213+
process._forceTickDone();
214+
process._fatalException(err);
192215
}
216+
193217
this.exit();
194218

195219
return ret;

lib/repl.js

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,10 @@ function REPLServer(prompt,
159159
regExMatcher.test(savedRegExMatches.join(sep));
160160

161161
if (!err) {
162-
try {
163-
if (self.useGlobal) {
164-
result = script.runInThisContext({ displayErrors: false });
165-
} else {
166-
result = script.runInContext(context, { displayErrors: false });
167-
}
168-
} catch (e) {
169-
err = e;
170-
if (err && process.domain) {
171-
debug('not recoverable, send to domain');
172-
process.domain.emit('error', err);
173-
process.domain.exit();
174-
return;
175-
}
162+
if (self.useGlobal) {
163+
result = script.runInThisContext({ displayErrors: false });
164+
} else {
165+
result = script.runInContext(context, { displayErrors: false });
176166
}
177167
}
178168

lib/timers.js

Lines changed: 80 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,30 @@ function listOnTimeout() {
8181
if (domain && domain._disposed)
8282
continue;
8383

84-
try {
85-
if (domain)
86-
domain.enter();
87-
threw = true;
88-
first._called = true;
89-
first._onTimeout();
90-
if (domain)
91-
domain.exit();
92-
threw = false;
93-
} finally {
94-
if (threw) {
95-
// We need to continue processing after domain error handling
96-
// is complete, but not by using whatever domain was left over
97-
// when the timeout threw its exception.
98-
var oldDomain = process.domain;
99-
process.domain = null;
100-
process.nextTick(listOnTimeoutNT, list);
101-
process.domain = oldDomain;
84+
// If there's no active domain or we're in the
85+
// error handler for the domain at the top of the stack,
86+
// don't use try/catch to allow --abort-on-uncaught-exception
87+
// to abort if an error is thrown.
88+
// Otherwise, use try/catch to get the chance to emit an
89+
// error on the current domain
90+
if (!domain || domain.emittingTopLevelError) {
91+
try {
92+
fireTimer(first);
93+
threw = false;
94+
} finally {
95+
if (threw) {
96+
postFireTimer();
97+
}
98+
}
99+
} else {
100+
try {
101+
fireTimer(first);
102+
threw = false;
103+
} catch (err) {
104+
process._fatalException(err);
105+
if (threw) {
106+
postFireTimer();
107+
}
102108
}
103109
}
104110
}
@@ -108,6 +114,26 @@ function listOnTimeout() {
108114
assert(L.isEmpty(list));
109115
list.close();
110116
delete lists[msecs];
117+
118+
function fireTimer(timer) {
119+
if (domain)
120+
domain.enter();
121+
threw = true;
122+
first._called = true;
123+
first._onTimeout();
124+
if (domain)
125+
domain.exit();
126+
}
127+
128+
function postFireTimer() {
129+
// We need to continue processing after domain error handling
130+
// is complete, but not by using whatever domain was left over
131+
// when the timeout threw its exception.
132+
var oldDomain = process.domain;
133+
process.domain = null;
134+
process.nextTick(listOnTimeoutNT, list);
135+
process.domain = oldDomain;
136+
}
111137
}
112138

113139

@@ -370,20 +396,28 @@ function processImmediate() {
370396
domain.enter();
371397

372398
var threw = true;
373-
try {
374-
immediate._onImmediate();
375-
threw = false;
376-
} finally {
377-
if (threw) {
378-
if (!L.isEmpty(queue)) {
379-
// Handle any remaining on next tick, assuming we're still
380-
// alive to do so.
381-
while (!L.isEmpty(immediateQueue)) {
382-
L.append(queue, L.shift(immediateQueue));
383-
}
384-
immediateQueue = queue;
385-
process.nextTick(processImmediate);
386-
}
399+
400+
// If there's no active domain or we're in the
401+
// error handler for the domain at the top of the stack,
402+
// don't use try/catch to allow --abort-on-uncaught-exception
403+
// to abort if an error is thrown.
404+
// Otherwise, use try/catch to get the chance to emit an
405+
// error on the current domain
406+
if (!domain || domain.emittingTopLevelError) {
407+
try {
408+
immediate._onImmediate();
409+
threw = false;
410+
} finally {
411+
postImmediate(threw);
412+
}
413+
} else {
414+
try {
415+
immediate._onImmediate();
416+
threw = false;
417+
} catch (err) {
418+
process._fatalException(err);
419+
420+
postImmediate(threw);
387421
}
388422
}
389423

@@ -397,6 +431,20 @@ function processImmediate() {
397431
if (L.isEmpty(immediateQueue)) {
398432
process._needImmediateCallback = false;
399433
}
434+
435+
function postImmediate(threw) {
436+
if (threw) {
437+
if (!L.isEmpty(queue)) {
438+
// Handle any remaining on next tick, assuming we're still
439+
// alive to do so.
440+
while (!L.isEmpty(immediateQueue)) {
441+
L.append(queue, L.shift(immediateQueue));
442+
}
443+
immediateQueue = queue;
444+
process.nextTick(processImmediate);
445+
}
446+
}
447+
}
400448
}
401449

402450

src/node.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,11 +2180,7 @@ void FatalException(Isolate* isolate,
21802180

21812181
if (false == caught->BooleanValue()) {
21822182
ReportException(env, error, message);
2183-
if (abort_on_uncaught_exception) {
2184-
ABORT();
2185-
} else {
2186-
exit(1);
2187-
}
2183+
exit(1);
21882184
}
21892185
}
21902186

@@ -3201,6 +3197,10 @@ static void ParseArgs(int* argc,
32013197
} else if (strcmp(arg, "--abort-on-uncaught-exception") == 0 ||
32023198
strcmp(arg, "--abort_on_uncaught_exception") == 0) {
32033199
abort_on_uncaught_exception = true;
3200+
// Pass this option to V8 anyway, because V8 uses it to determine
3201+
// if it needs to abort on an uncaught exception
3202+
new_v8_argv[new_v8_argc] = arg;
3203+
new_v8_argc += 1;
32043204
} else if (strcmp(arg, "--v8-options") == 0) {
32053205
new_v8_argv[new_v8_argc] = "--help";
32063206
new_v8_argc += 1;

0 commit comments

Comments
 (0)