Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
process: make tick callback and promise rejection callback more robust
- Rename `internalTickCallback` to `processTicksAndRejections`, make
  sure it does not get called if it's not set in C++.
- Rename `emitPromiseRejectionWarnings` to `processPromiseRejections`
  since it also emit events that are not warnings.
- Sets `SetPromiseRejectCallback` in the `Environment` constructor
  to make sure it only gets called once per-isolate, and make
  sure it does not get called if it's not set in C++.
- Wrap promise rejection callback initialization into
  `listenForRejections()`.
- Add comments.
  • Loading branch information
joyeecheung committed Jan 4, 2019
commit 8f780213c98626c466aff5abc31f1dafb48874d6
20 changes: 9 additions & 11 deletions lib/internal/process/next_tick.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ const {
tickInfo,
// Used to run V8's micro task queue.
runMicrotasks,
setTickCallback,
initializePromiseRejectCallback
setTickCallback
} = internalBinding('task_queue');

const {
setHasRejectionToWarn,
hasRejectionToWarn,
promiseRejectHandler,
emitPromiseRejectionWarnings
listenForRejections,
processPromiseRejections
} = require('internal/process/promises');

const {
Expand Down Expand Up @@ -49,10 +48,10 @@ function runNextTicks() {
if (!hasTickScheduled() && !hasRejectionToWarn())
return;

internalTickCallback();
processTicksAndRejections();
}

function internalTickCallback() {
function processTicksAndRejections() {
let tock;
do {
while (tock = queue.shift()) {
Expand Down Expand Up @@ -80,7 +79,7 @@ function internalTickCallback() {
}
setHasTickScheduled(false);
runMicrotasks();
} while (!queue.isEmpty() || emitPromiseRejectionWarnings());
} while (!queue.isEmpty() || processPromiseRejections());
setHasRejectionToWarn(false);
}

Expand Down Expand Up @@ -134,11 +133,10 @@ function nextTick(callback) {
// TODO(joyeecheung): make this a factory class so that node.js can
// control the side effects caused by the initializers.
exports.setup = function() {
// Initializes the per-isolate promise rejection callback which
// will call the handler being passed into this function.
initializePromiseRejectCallback(promiseRejectHandler);
// Sets the per-isolate promise rejection callback
listenForRejections();
// Sets the callback to be run in every tick.
setTickCallback(internalTickCallback);
setTickCallback(processTicksAndRejections);
return {
nextTick,
runNextTicks
Expand Down
15 changes: 11 additions & 4 deletions lib/internal/process/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const {
kPromiseHandlerAddedAfterReject,
kPromiseResolveAfterResolved,
kPromiseRejectAfterResolved
}
},
setPromiseRejectCallback
} = internalBinding('task_queue');

// *Must* match Environment::TickInfo::Fields in src/env.h.
Expand Down Expand Up @@ -118,7 +119,9 @@ function emitDeprecationWarning() {
}
}

function emitPromiseRejectionWarnings() {
// If this method returns true, at least one more tick need to be
// scheduled to process any potential pending rejections
function processPromiseRejections() {
while (asyncHandledRejections.length > 0) {
const { promise, warning } = asyncHandledRejections.shift();
if (!process.emit('rejectionHandled', promise)) {
Expand All @@ -143,9 +146,13 @@ function emitPromiseRejectionWarnings() {
return maybeScheduledTicks || pendingUnhandledRejections.length !== 0;
}

function listenForRejections() {
setPromiseRejectCallback(promiseRejectHandler);
Comment thread
joyeecheung marked this conversation as resolved.
Outdated
}

module.exports = {
hasRejectionToWarn,
setHasRejectionToWarn,
promiseRejectHandler,
emitPromiseRejectionWarnings
listenForRejections,
processPromiseRejections
};
10 changes: 8 additions & 2 deletions src/callback_scope.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace node {

using v8::Function;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
Expand Down Expand Up @@ -114,8 +115,13 @@ void InternalCallbackScope::Close() {

if (!env_->can_call_into_js()) return;

if (env_->tick_callback_function()
->Call(env_->context(), process, 0, nullptr).IsEmpty()) {
Local<Function> tick_callback = env_->tick_callback_function();

// The tick is triggered before JS land calls SetTickCallback
// to initializes the tick callback during bootstrap.
CHECK(!tick_callback.IsEmpty());

if (tick_callback->Call(env_->context(), process, 0, nullptr).IsEmpty()) {
failed_ = true;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ Environment::Environment(IsolateData* isolate_data,
if (options_->no_force_async_hooks_checks) {
async_hooks_.no_force_checks();
}

isolate()->SetPromiseRejectCallback(task_queue::PromiseRejectCallback);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave a TODO comment for me? We’re letting a single Environment take control of per-Isolate state here – I know it’s been that way before, but it’s not what we should be doing…

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity - when would multiple Environments correspond to an isolate?

}

Environment::~Environment() {
Expand Down
4 changes: 4 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ SlicedArguments::SlicedArguments(
size_ = size;
}

namespace task_queue {
void PromiseRejectCallback(v8::PromiseRejectMessage message);
} // namespace task_queue

v8::Maybe<bool> ProcessEmitWarning(Environment* env, const char* fmt, ...);
v8::Maybe<bool> ProcessEmitDeprecationWarning(Environment* env,
const char* warning,
Expand Down
17 changes: 8 additions & 9 deletions src/node_task_queue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ static void SetTickCallback(const FunctionCallbackInfo<Value>& args) {
env->set_tick_callback_function(args[0].As<Function>());
}

static void PromiseRejectCallback(PromiseRejectMessage message) {
void PromiseRejectCallback(PromiseRejectMessage message) {
static std::atomic<uint64_t> unhandledRejections{0};
static std::atomic<uint64_t> rejectionsHandledAfter{0};

Expand All @@ -49,6 +49,10 @@ static void PromiseRejectCallback(PromiseRejectMessage message) {
if (env == nullptr) return;

Local<Function> callback = env->promise_reject_callback();
// The promise is rejected before JS land calls SetPromiseRejectCallback
// to initializes the promise reject callback during bootstrap.
CHECK(!callback.IsEmpty());

Local<Value> value;
Local<Value> type = Number::New(env->isolate(), event);

Expand Down Expand Up @@ -83,17 +87,12 @@ static void PromiseRejectCallback(PromiseRejectMessage message) {
env->context(), Undefined(isolate), arraysize(args), args));
}

static void InitializePromiseRejectCallback(
static void SetPromiseRejectCallback(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();

CHECK(args[0]->IsFunction());

// TODO(joyeecheung): this may be moved to somewhere earlier in the bootstrap
// to make sure it's only called once
isolate->SetPromiseRejectCallback(PromiseRejectCallback);

env->set_promise_reject_callback(args[0].As<Function>());
}

Expand All @@ -120,8 +119,8 @@ static void Initialize(Local<Object> target,
FIXED_ONE_BYTE_STRING(isolate, "promiseRejectEvents"),
events).FromJust();
env->SetMethod(target,
"initializePromiseRejectCallback",
InitializePromiseRejectCallback);
"setPromiseRejectCallback",
SetPromiseRejectCallback);
}

} // namespace task_queue
Expand Down
2 changes: 1 addition & 1 deletion test/message/events_unhandled_error_nexttick.out
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Error
at startup (internal/bootstrap/node.js:*:*)
Emitted 'error' event at:
at process.nextTick (*events_unhandled_error_nexttick.js:*:*)
at internalTickCallback (internal/process/next_tick.js:*:*)
at processTicksAndRejections (internal/process/next_tick.js:*:*)
at process.runNextTicks [as _tickCallback] (internal/process/next_tick.js:*:*)
at Function.Module.runMain (internal/modules/cjs/loader.js:*:*)
at executeUserCode (internal/bootstrap/node.js:*:*)
Expand Down
2 changes: 1 addition & 1 deletion test/message/nexttick_throw.out
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
^
ReferenceError: undefined_reference_error_maker is not defined
at *test*message*nexttick_throw.js:*:*
at internalTickCallback (internal/process/next_tick.js:*:*)
at processTicksAndRejections (internal/process/next_tick.js:*:*)
at process.runNextTicks [as _tickCallback] (internal/process/next_tick.js:*:*)
at Function.Module.runMain (internal/modules/cjs/loader.js:*:*)
at executeUserCode (internal/bootstrap/node.js:*:*)
Expand Down
8 changes: 4 additions & 4 deletions test/message/stdin_messages.out
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ SyntaxError: Strict mode code may not include a with statement
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at process.internalTickCallback (internal/process/next_tick.js:*:*)
at processTicksAndRejections (internal/process/next_tick.js:*:*)
42
42
[stdin]:1
Expand All @@ -29,7 +29,7 @@ Error: hello
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at process.internalTickCallback (internal/process/next_tick.js:*:*)
at processTicksAndRejections (internal/process/next_tick.js:*:*)
[stdin]:1
throw new Error("hello")
^
Expand All @@ -44,7 +44,7 @@ Error: hello
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at process.internalTickCallback (internal/process/next_tick.js:*:*)
at processTicksAndRejections (internal/process/next_tick.js:*:*)
100
[stdin]:1
var x = 100; y = x;
Expand All @@ -60,7 +60,7 @@ ReferenceError: y is not defined
at Socket.process.stdin.on (internal/bootstrap/node.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at process.internalTickCallback (internal/process/next_tick.js:*:*)
at processTicksAndRejections (internal/process/next_tick.js:*:*)

[stdin]:1
var ______________________________________________; throw 10
Expand Down