Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit caeb677

Browse files
Julien Gillitrevnorris
Julien Gilli
authored andcommitted
domains: fix issues with abort on uncaught
Do not abort the process if an error is thrown from within a domain, an error handler is setup for the domain and --abort-on-uncaught-exception was passed on the command line. However, if an error is thrown from within the top-level domain's error handler and --abort-on-uncaught-exception was passed on the command line, make the process abort. Fixes: #8631 Fixes: #8630 PR-URL: #8666 Reviewed-by: Trevor Norris <[email protected]>
1 parent fbff705 commit caeb677

4 files changed

+364
-31
lines changed

src/node.cc

+21
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ static Persistent<String> enter_symbol;
126126
static Persistent<String> exit_symbol;
127127
static Persistent<String> disposed_symbol;
128128

129+
static Persistent<String> emitting_toplevel_domain_error_symbol;
129130

130131
static bool print_eval = false;
131132
static bool force_repl = false;
@@ -904,6 +905,20 @@ Handle<Value> FromConstructorTemplate(Persistent<FunctionTemplate> t,
904905
return scope.Close(t->GetFunction()->NewInstance(argc, argv));
905906
}
906907

908+
static bool IsDomainActive() {
909+
if (domain_symbol.IsEmpty())
910+
domain_symbol = NODE_PSYMBOL("domain");
911+
912+
Local<Value> domain_v = process->Get(domain_symbol);
913+
914+
return domain_v->IsObject();
915+
}
916+
917+
bool ShouldAbortOnUncaughtException() {
918+
Local<Value> emitting_toplevel_domain_error_v =
919+
process->Get(emitting_toplevel_domain_error_symbol);
920+
return !IsDomainActive() || emitting_toplevel_domain_error_v->BooleanValue();
921+
}
907922

908923
Handle<Value> UsingDomains(const Arguments& args) {
909924
HandleScope scope;
@@ -2437,6 +2452,11 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
24372452
// pre-set _events object for faster emit checks
24382453
process->Set(String::NewSymbol("_events"), Object::New());
24392454

2455+
if (emitting_toplevel_domain_error_symbol.IsEmpty())
2456+
emitting_toplevel_domain_error_symbol =
2457+
NODE_PSYMBOL("_emittingTopLevelDomainError");
2458+
process->Set(emitting_toplevel_domain_error_symbol, False());
2459+
24402460
return process;
24412461
}
24422462

@@ -2959,6 +2979,7 @@ char** Init(int argc, char *argv[]) {
29592979
// Fetch a reference to the main isolate, so we have a reference to it
29602980
// even when we need it to access it from another (debugger) thread.
29612981
node_isolate = Isolate::GetCurrent();
2982+
node_isolate->SetAbortOnUncaughtException(ShouldAbortOnUncaughtException);
29622983

29632984
// If the --debug flag was specified then initialize the debug thread.
29642985
if (use_debug_agent) {

src/node.js

+54-31
Original file line numberDiff line numberDiff line change
@@ -236,37 +236,60 @@
236236

237237
er.domain = domain;
238238
er.domainThrown = true;
239-
// wrap this in a try/catch so we don't get infinite throwing
240-
try {
241-
// One of three things will happen here.
242-
//
243-
// 1. There is a handler, caught = true
244-
// 2. There is no handler, caught = false
245-
// 3. It throws, caught = false
246-
//
247-
// If caught is false after this, then there's no need to exit()
248-
// the domain, because we're going to crash the process anyway.
249-
caught = domain.emit('error', er);
250-
251-
// Exit all domains on the stack. Uncaught exceptions end the
252-
// current tick and no domains should be left on the stack
253-
// between ticks.
254-
var domainModule = NativeModule.require('domain');
255-
domainStack.length = 0;
256-
domainModule.active = process.domain = null;
257-
} catch (er2) {
258-
// The domain error handler threw! oh no!
259-
// See if another domain can catch THIS error,
260-
// or else crash on the original one.
261-
// If the user already exited it, then don't double-exit.
262-
if (domain === domainModule.active)
263-
domainStack.pop();
264-
if (domainStack.length) {
265-
var parentDomain = domainStack[domainStack.length - 1];
266-
process.domain = domainModule.active = parentDomain;
267-
caught = process._fatalException(er2);
268-
} else
269-
caught = false;
239+
240+
// The top-level domain-handler is handled separately.
241+
//
242+
// The reason is that if V8 was passed a command line option
243+
// asking it to abort on an uncaught exception (currently
244+
// "--abort-on-uncaught-exception"), we want an uncaught exception
245+
// in the top-level domain error handler to make the
246+
// process abort. Using try/catch here would always make V8 think
247+
// that these exceptions are caught, and thus would prevent it from
248+
// aborting in these cases.
249+
if (domainStack.length === 1) {
250+
try {
251+
// Set the _emittingTopLevelDomainError so that we know that, even
252+
// if technically the top-level domain is still active, it would
253+
// be ok to abort on an uncaught exception at this point
254+
process._emittingTopLevelDomainError = true;
255+
caught = domain.emit('error', er);
256+
} finally {
257+
process._emittingTopLevelDomainError = false;
258+
}
259+
} else {
260+
// wrap this in a try/catch so we don't get infinite throwing
261+
try {
262+
// One of three things will happen here.
263+
//
264+
// 1. There is a handler, caught = true
265+
// 2. There is no handler, caught = false
266+
// 3. It throws, caught = false
267+
//
268+
// If caught is false after this, then there's no need to exit()
269+
// the domain, because we're going to crash the process anyway.
270+
caught = domain.emit('error', er);
271+
272+
// Exit all domains on the stack. Uncaught exceptions end the
273+
// current tick and no domains should be left on the stack
274+
// between ticks.
275+
var domainModule = NativeModule.require('domain');
276+
domainStack.length = 0;
277+
domainModule.active = process.domain = null;
278+
} catch (er2) {
279+
// The domain error handler threw! oh no!
280+
// See if another domain can catch THIS error,
281+
// or else crash on the original one.
282+
// If the user already exited it, then don't double-exit.
283+
if (domain === domainModule.active)
284+
domainStack.pop();
285+
if (domainStack.length) {
286+
var parentDomain = domainStack[domainStack.length - 1];
287+
process.domain = domainModule.active = parentDomain;
288+
caught = process._fatalException(er2);
289+
} else {
290+
caught = false;
291+
}
292+
}
270293
}
271294
} else {
272295
caught = process.emit('uncaughtException', er);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
/*
23+
* The goal of this test is to make sure that when a top-level error
24+
* handler throws an error following the handling of a previous error,
25+
* the process reports the error message from the error thrown in the
26+
* top-level error handler, not the one from the previous error.
27+
*/
28+
29+
var domainErrHandlerExMessage = 'exception from domain error handler';
30+
var internalExMessage = 'You should NOT see me';
31+
32+
if (process.argv[2] === 'child') {
33+
var domain = require('domain');
34+
var d = domain.create();
35+
36+
d.on('error', function() {
37+
throw new Error(domainErrHandlerExMessage);
38+
});
39+
40+
d.run(function doStuff() {
41+
process.nextTick(function () {
42+
throw new Error(internalExMessage);
43+
});
44+
});
45+
} else {
46+
var fork = require('child_process').fork;
47+
var assert = require('assert');
48+
49+
function test() {
50+
var child = fork(process.argv[1], ['child'], {silent:true});
51+
var gotDataFromStderr = false;
52+
var stderrOutput = '';
53+
if (child) {
54+
child.stderr.on('data', function onStderrData(data) {
55+
gotDataFromStderr = true;
56+
stderrOutput += data.toString();
57+
})
58+
59+
child.on('exit', function onChildExited(exitCode, signal) {
60+
assert(gotDataFromStderr);
61+
assert(stderrOutput.indexOf(domainErrHandlerExMessage) !== -1);
62+
assert(stderrOutput.indexOf(internalExMessage) === -1);
63+
64+
var expectedExitCode = 7;
65+
var expectedSignal = null;
66+
67+
assert.equal(exitCode, expectedExitCode);
68+
assert.equal(signal, expectedSignal);
69+
});
70+
}
71+
}
72+
73+
test();
74+
}

0 commit comments

Comments
 (0)