Skip to content

Commit 22cd537

Browse files
devsnektargos
authored andcommitted
lib: trigger uncaught exception handler for microtasks
PR-URL: #23794 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Matheus Marchini <[email protected]>
1 parent b07cb48 commit 22cd537

File tree

5 files changed

+52
-25
lines changed

5 files changed

+52
-25
lines changed

doc/api/globals.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ added: v11.0.0
119119
* `callback` {Function} Function to be queued.
120120

121121
The `queueMicrotask()` method queues a microtask to invoke `callback`. If
122-
`callback` throws an exception, the [`process` object][] `'error'` event will
123-
be emitted.
122+
`callback` throws an exception, the [`process` object][] `'uncaughtException'`
123+
event will be emitted.
124124

125125
In general, `queueMicrotask` is the idiomatic choice over `process.nextTick()`.
126126
`process.nextTick()` will always run before the microtask queue, and so

lib/internal/bootstrap/node.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
_umask, _initgroups, _setegid, _seteuid,
2525
_setgid, _setuid, _setgroups,
2626
_shouldAbortOnUncaughtToggle },
27-
{ internalBinding, NativeModule }) {
27+
{ internalBinding, NativeModule },
28+
triggerFatalException) {
2829
const exceptionHandlerState = { captureFn: null };
2930
const isMainThread = internalBinding('worker').threadId === 0;
3031

@@ -538,8 +539,9 @@
538539
get: () => {
539540
process.emitWarning('queueMicrotask() is experimental.',
540541
'ExperimentalWarning');
541-
const { queueMicrotask } =
542+
const { setupQueueMicrotask } =
542543
NativeModule.require('internal/queue_microtask');
544+
const queueMicrotask = setupQueueMicrotask(triggerFatalException);
543545
Object.defineProperty(global, 'queueMicrotask', {
544546
value: queueMicrotask,
545547
writable: true,

lib/internal/queue_microtask.js

+27-19
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,35 @@ const { AsyncResource } = require('async_hooks');
55
const { getDefaultTriggerAsyncId } = require('internal/async_hooks');
66
const { enqueueMicrotask } = internalBinding('util');
77

8-
// declared separately for name, arrow function to prevent construction
9-
const queueMicrotask = (callback) => {
10-
if (typeof callback !== 'function') {
11-
throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);
12-
}
8+
const setupQueueMicrotask = (triggerFatalException) => {
9+
const queueMicrotask = (callback) => {
10+
if (typeof callback !== 'function') {
11+
throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);
12+
}
1313

14-
const asyncResource = new AsyncResource('Microtask', {
15-
triggerAsyncId: getDefaultTriggerAsyncId(),
16-
requireManualDestroy: true,
17-
});
14+
const asyncResource = new AsyncResource('Microtask', {
15+
triggerAsyncId: getDefaultTriggerAsyncId(),
16+
requireManualDestroy: true,
17+
});
1818

19-
enqueueMicrotask(() => {
20-
asyncResource.runInAsyncScope(() => {
21-
try {
22-
callback();
23-
} catch (e) {
24-
process.emit('error', e);
25-
}
19+
enqueueMicrotask(() => {
20+
asyncResource.runInAsyncScope(() => {
21+
try {
22+
callback();
23+
} catch (error) {
24+
// TODO(devsnek) remove this if
25+
// https://bugs.chromium.org/p/v8/issues/detail?id=8326
26+
// is resolved such that V8 triggers the fatal exception
27+
// handler for microtasks
28+
triggerFatalException(error);
29+
} finally {
30+
asyncResource.emitDestroy();
31+
}
32+
});
2633
});
27-
asyncResource.emitDestroy();
28-
});
34+
};
35+
36+
return queueMicrotask;
2937
};
3038

31-
module.exports = { queueMicrotask };
39+
module.exports = { setupQueueMicrotask };

src/node.cc

+18-1
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,18 @@ void FatalException(Isolate* isolate, const TryCatch& try_catch) {
14231423
}
14241424

14251425

1426+
static void FatalException(const FunctionCallbackInfo<Value>& args) {
1427+
Isolate* isolate = args.GetIsolate();
1428+
Environment* env = Environment::GetCurrent(isolate);
1429+
if (env != nullptr && env->abort_on_uncaught_exception()) {
1430+
Abort();
1431+
}
1432+
Local<Value> exception = args[0];
1433+
Local<Message> message = Exception::CreateMessage(isolate, exception);
1434+
FatalException(isolate, exception, message);
1435+
}
1436+
1437+
14261438
static void OnMessage(Local<Message> message, Local<Value> error) {
14271439
// The current version of V8 sends messages for errors only
14281440
// (thus `error` is always set).
@@ -2161,14 +2173,19 @@ void LoadEnvironment(Environment* env) {
21612173
return;
21622174
}
21632175

2176+
Local<Function> trigger_fatal_exception =
2177+
env->NewFunctionTemplate(FatalException)->GetFunction(env->context())
2178+
.ToLocalChecked();
2179+
21642180
// Bootstrap Node.js
21652181
Local<Object> bootstrapper = Object::New(env->isolate());
21662182
SetupBootstrapObject(env, bootstrapper);
21672183
Local<Value> bootstrapped_node;
21682184
Local<Value> node_bootstrapper_args[] = {
21692185
env->process_object(),
21702186
bootstrapper,
2171-
bootstrapped_loaders
2187+
bootstrapped_loaders,
2188+
trigger_fatal_exception,
21722189
};
21732190
if (!ExecuteBootstrapper(env, node_bootstrapper.ToLocalChecked(),
21742191
arraysize(node_bootstrapper_args),

test/parallel/test-queue-microtask.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ queueMicrotask(common.mustCall(function() {
4242
}
4343

4444
const eq = [];
45-
process.on('error', (e) => {
45+
process.on('uncaughtException', (e) => {
4646
eq.push(e);
4747
});
4848

0 commit comments

Comments
 (0)