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

Commit fd61ee3

Browse files
author
Julien Gilli
committed
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. Also fixes #8630.
1 parent 523929c commit fd61ee3

File tree

8 files changed

+364
-34
lines changed

8 files changed

+364
-34
lines changed

deps/v8/include/v8.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2856,7 +2856,8 @@ class V8EXPORT Isolate {
28562856
* restored when exiting. Re-entering an isolate is allowed.
28572857
*/
28582858
void Enter();
2859-
2859+
typedef bool (*should_abort_on_uncaught_exception_handler_t)();
2860+
void SetShouldAbortOnUncaughtExceptionHandler(should_abort_on_uncaught_exception_handler_t handler);
28602861
/**
28612862
* Exits this isolate by restoring the previously entered one in the
28622863
* current thread. The isolate may still stay the same, if it was

deps/v8/src/api.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5565,6 +5565,10 @@ void Isolate::Enter() {
55655565
isolate->Enter();
55665566
}
55675567

5568+
void Isolate::SetShouldAbortOnUncaughtExceptionHandler(should_abort_on_uncaught_exception_handler_t handler) {
5569+
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
5570+
isolate->SetShouldAbortOnUncaughtExceptionHandler(handler);
5571+
}
55685572

55695573
void Isolate::Exit() {
55705574
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);

deps/v8/src/isolate.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,7 +1158,8 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
11581158
// print a user-friendly stack trace (not an internal one).
11591159
if (fatal_exception_depth == 0 &&
11601160
FLAG_abort_on_uncaught_exception &&
1161-
(report_exception || can_be_caught_externally)) {
1161+
(report_exception || can_be_caught_externally) &&
1162+
(!should_abort_on_uncaught_exception_handler_ || should_abort_on_uncaught_exception_handler_())) {
11621163
fatal_exception_depth++;
11631164
fprintf(stderr, "%s\n\nFROM\n",
11641165
*MessageHandler::GetLocalizedMessage(message_obj));
@@ -1339,6 +1340,9 @@ void Isolate::SetCaptureStackTraceForUncaughtExceptions(
13391340
stack_trace_for_uncaught_exceptions_options_ = options;
13401341
}
13411342

1343+
void Isolate::SetShouldAbortOnUncaughtExceptionHandler(v8::Isolate::should_abort_on_uncaught_exception_handler_t handler) {
1344+
should_abort_on_uncaught_exception_handler_ = handler;
1345+
}
13421346

13431347
bool Isolate::is_out_of_memory() {
13441348
if (has_pending_exception()) {
@@ -1534,7 +1538,8 @@ Isolate::Isolate()
15341538
date_cache_(NULL),
15351539
context_exit_happened_(false),
15361540
deferred_handles_head_(NULL),
1537-
optimizing_compiler_thread_(this) {
1541+
optimizing_compiler_thread_(this),
1542+
should_abort_on_uncaught_exception_handler_(0) {
15381543
TRACE_ISOLATE(constructor);
15391544

15401545
memset(isolate_addresses_, 0,

deps/v8/src/isolate.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,9 @@ class Isolate {
692692
int frame_limit,
693693
StackTrace::StackTraceOptions options);
694694

695+
typedef bool (*should_abort_on_uncaught_exception_handler_t)();
696+
void SetShouldAbortOnUncaughtExceptionHandler(should_abort_on_uncaught_exception_handler_t handler);
697+
695698
// Tells whether the current context has experienced an out of memory
696699
// exception.
697700
bool is_out_of_memory();
@@ -1292,6 +1295,8 @@ class Isolate {
12921295
DeferredHandles* deferred_handles_head_;
12931296
OptimizingCompilerThread optimizing_compiler_thread_;
12941297

1298+
should_abort_on_uncaught_exception_handler_t should_abort_on_uncaught_exception_handler_;
1299+
12951300
friend class ExecutionAccess;
12961301
friend class HandleScopeImplementer;
12971302
friend class IsolateInitializer;

src/node.cc

Lines changed: 30 additions & 0 deletions
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,29 @@ 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+
bool has_domain = domain_v->IsObject();
915+
if (has_domain) {
916+
Local<Object> domain = domain_v->ToObject();
917+
assert(!domain.IsEmpty());
918+
if (!domain->IsNull()) {
919+
return true;
920+
}
921+
}
922+
923+
return false;
924+
}
925+
926+
bool ShouldAbortOnUncaughtException() {
927+
Local<Value> emitting_toplevel_domain_error_v =
928+
process->Get(emitting_toplevel_domain_error_symbol);
929+
return !IsDomainActive() || emitting_toplevel_domain_error_v->BooleanValue();
930+
}
907931

908932
Handle<Value> UsingDomains(const Arguments& args) {
909933
HandleScope scope;
@@ -2437,6 +2461,11 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
24372461
// pre-set _events object for faster emit checks
24382462
process->Set(String::NewSymbol("_events"), Object::New());
24392463

2464+
if (emitting_toplevel_domain_error_symbol.IsEmpty())
2465+
emitting_toplevel_domain_error_symbol =
2466+
NODE_PSYMBOL("_emittingTopLevelDomainError");
2467+
process->Set(emitting_toplevel_domain_error_symbol, False());
2468+
24402469
return process;
24412470
}
24422471

@@ -2959,6 +2988,7 @@ char** Init(int argc, char *argv[]) {
29592988
// Fetch a reference to the main isolate, so we have a reference to it
29602989
// even when we need it to access it from another (debugger) thread.
29612990
node_isolate = Isolate::GetCurrent();
2991+
node_isolate->SetShouldAbortOnUncaughtExceptionHandler(ShouldAbortOnUncaughtException);
29622992

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

src/node.js

Lines changed: 54 additions & 31 deletions
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);
Lines changed: 74 additions & 0 deletions
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)