Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v9.x-backport] Backport #17198, #17738, #17841, #17736, #17881, #17879 and #18139 #19006

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 32 additions & 0 deletions doc/api/timers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,38 @@ This object is created internally and is returned from [`setImmediate()`][]. It
can be passed to [`clearImmediate()`][] in order to cancel the scheduled
actions.

By default, when an immediate is scheduled, the Node.js event loop will continue
running as long as the immediate is active. The `Immediate` object returned by
[`setImmediate()`][] exports both `immediate.ref()` and `immediate.unref()`
functions that can be used to control this default behavior.

### immediate.ref()
<!-- YAML
added: REPLACEME
-->

When called, requests that the Node.js event loop *not* exit so long as the
`Immediate` is active. Calling `immediate.ref()` multiple times will have no
effect.

*Note*: By default, all `Immediate` objects are "ref'd", making it normally
unnecessary to call `immediate.ref()` unless `immediate.unref()` had been called
previously.

Returns a reference to the `Immediate`.

### immediate.unref()
<!-- YAML
added: REPLACEME
-->

When called, the active `Immediate` object will not require the Node.js event
loop to remain active. If there is no other activity keeping the event loop
running, the process may exit before the `Immediate` object's callback is
invoked. Calling `immediate.unref()` multiple times will have no effect.

Returns a reference to the `Immediate`.

## Class: Timeout

This object is created internally and is returned from [`setTimeout()`][] and
Expand Down
49 changes: 22 additions & 27 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@
}
}

function noop() {}

function setupProcessFatal() {
const async_wrap = process.binding('async_wrap');
// Arrays containing hook flags and ids for async_hook calls.
Expand All @@ -419,23 +421,15 @@
kDefaultTriggerAsyncId, kStackLength } = async_wrap.constants;

process._fatalException = function(er) {
var caught;

// It's possible that kDefaultTriggerAsyncId was set for a constructor
// call that threw and was never cleared. So clear it now.
async_id_fields[kDefaultTriggerAsyncId] = -1;

if (exceptionHandlerState.captureFn !== null) {
exceptionHandlerState.captureFn(er);
caught = true;
}

if (!caught)
caught = process.emit('uncaughtException', er);

// If someone handled it, then great. otherwise, die in C++ land
// since that means that we'll exit the process, emit the 'exit' event
if (!caught) {
} else if (!process.emit('uncaughtException', er)) {
// If someone handled it, then great. otherwise, die in C++ land
// since that means that we'll exit the process, emit the 'exit' event
try {
if (!process._exiting) {
process._exiting = true;
Expand All @@ -444,24 +438,25 @@
} catch (er) {
// nothing to be done about it at this point.
}
return false;
}

// If we handled an error, then make sure any ticks get processed
// by ensuring that the next Immediate cycle isn't empty
NativeModule.require('timers').setImmediate(noop);

// Emit the after() hooks now that the exception has been handled.
if (async_hook_fields[kAfter] > 0) {
const { emitAfter } = NativeModule.require('internal/async_hooks');
do {
emitAfter(async_id_fields[kExecutionAsyncId]);
} while (async_hook_fields[kStackLength] > 0);
// Or completely empty the id stack.
} else {
// If we handled an error, then make sure any ticks get processed
NativeModule.require('timers').setImmediate(process._tickCallback);

// Emit the after() hooks now that the exception has been handled.
if (async_hook_fields[kAfter] > 0) {
do {
NativeModule.require('internal/async_hooks').emitAfter(
async_id_fields[kExecutionAsyncId]);
} while (async_hook_fields[kStackLength] > 0);
// Or completely empty the id stack.
} else {
clearAsyncIdStack();
}
clearAsyncIdStack();
}

return caught;
return true;
};
}

Expand Down Expand Up @@ -634,7 +629,7 @@
};

NativeModule.wrapper = [
'(function (exports, require, module, internalBinding) {',
'(function (exports, require, module, internalBinding, process) {',
'\n});'
];

Expand All @@ -650,7 +645,7 @@
lineOffset: 0,
displayErrors: true
});
fn(this.exports, NativeModule.require, this, internalBinding);
fn(this.exports, NativeModule.require, this, internalBinding, process);

this.loaded = true;
} finally {
Expand Down
150 changes: 49 additions & 101 deletions lib/internal/process/next_tick.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,9 @@
'use strict';

// This value is used to prevent the nextTickQueue from becoming too
// large and cause the process to run out of memory. When this value
// is reached the nextTimeQueue array will be shortened (see tickDone
// for details).
const kMaxCallbacksPerLoop = 1e4;

exports.setup = setupNextTick;
// Will be overwritten when setupNextTick() is called.
exports.nextTick = null;

class NextTickQueue {
constructor() {
this.head = null;
this.tail = null;
}

push(v) {
const entry = { data: v, next: null };
if (this.tail !== null)
this.tail.next = entry;
else
this.head = entry;
this.tail = entry;
}

shift() {
if (this.head === null)
return;
const ret = this.head.data;
if (this.head === this.tail)
this.head = this.tail = null;
else
this.head = this.head.next;
return ret;
}

clear() {
this.head = null;
this.tail = null;
}
}

function setupNextTick() {
const async_wrap = process.binding('async_wrap');
const async_hooks = require('internal/async_hooks');
Expand All @@ -56,15 +18,47 @@ function setupNextTick() {
// Grab the constants necessary for working with internal arrays.
const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
const { async_id_symbol, trigger_async_id_symbol } = async_wrap;
const nextTickQueue = new NextTickQueue();
var microtasksScheduled = false;

// Used to run V8's micro task queue.
var _runMicrotasks = {};
// tickInfo is used so that the C++ code in src/node.cc can
// have easy access to our nextTick state, and avoid unnecessary
// calls into JS land.
// runMicrotasks is used to run V8's micro task queue.
const [
tickInfo,
runMicrotasks
] = process._setupNextTick(_tickCallback);

// *Must* match Environment::TickInfo::Fields in src/env.h.
var kIndex = 0;
var kLength = 1;
const kHasScheduled = 0;

const nextTickQueue = {
head: null,
tail: null,
push(data) {
const entry = { data, next: null };
if (this.tail !== null) {
this.tail.next = entry;
} else {
this.head = entry;
tickInfo[kHasScheduled] = 1;
}
this.tail = entry;
},
shift() {
if (this.head === null)
return;
const ret = this.head.data;
if (this.head === this.tail) {
this.head = this.tail = null;
tickInfo[kHasScheduled] = 0;
} else {
this.head = this.head.next;
}
return ret;
}
};

var microtasksScheduled = false;

process.nextTick = nextTick;
// Needs to be accessible from beyond this scope.
Expand All @@ -73,25 +67,6 @@ function setupNextTick() {
// Set the nextTick() function for internal usage.
exports.nextTick = internalNextTick;

// This tickInfo thing is used so that the C++ code in src/node.cc
// can have easy access to our nextTick state, and avoid unnecessary
// calls into JS land.
const tickInfo = process._setupNextTick(_tickCallback, _runMicrotasks);

_runMicrotasks = _runMicrotasks.runMicrotasks;

function tickDone() {
if (tickInfo[kLength] !== 0) {
if (tickInfo[kLength] <= tickInfo[kIndex]) {
nextTickQueue.clear();
tickInfo[kLength] = 0;
} else {
tickInfo[kLength] -= tickInfo[kIndex];
}
}
tickInfo[kIndex] = 0;
}

const microTasksTickObject = {
callback: runMicrotasksCallback,
args: undefined,
Expand All @@ -105,38 +80,27 @@ function setupNextTick() {
// For the moment all microtasks come from the void until the PromiseHook
// API is implemented.
nextTickQueue.push(microTasksTickObject);

tickInfo[kLength]++;
microtasksScheduled = true;
}

function runMicrotasksCallback() {
microtasksScheduled = false;
_runMicrotasks();
runMicrotasks();

if (tickInfo[kIndex] < tickInfo[kLength] ||
emitPendingUnhandledRejections()) {
if (nextTickQueue.head !== null || emitPendingUnhandledRejections())
scheduleMicrotasks();
}
}

function _tickCallback() {
let tock;
do {
while (tickInfo[kIndex] < tickInfo[kLength]) {
++tickInfo[kIndex];
const tock = nextTickQueue.shift();

// CHECK(Number.isSafeInteger(tock[async_id_symbol]))
// CHECK(tock[async_id_symbol] > 0)
// CHECK(Number.isSafeInteger(tock[trigger_async_id_symbol]))
// CHECK(tock[trigger_async_id_symbol] > 0)

while (tock = nextTickQueue.shift()) {
const asyncId = tock[async_id_symbol];
emitBefore(asyncId, tock[trigger_async_id_symbol]);
// emitDestroy() places the async_id_symbol into an asynchronous queue
// that calls the destroy callback in the future. It's called before
// calling tock.callback so destroy will be called even if the callback
// throws an exception that is handles by 'uncaughtException' or a
// throws an exception that is handled by 'uncaughtException' or a
// domain.
// TODO(trevnorris): This is a bit of a hack. It relies on the fact
// that nextTick() doesn't allow the event loop to proceed, but if
Expand All @@ -152,24 +116,21 @@ function setupNextTick() {
Reflect.apply(callback, undefined, tock.args);

emitAfter(asyncId);

if (kMaxCallbacksPerLoop < tickInfo[kIndex])
tickDone();
}
tickDone();
_runMicrotasks();
runMicrotasks();
emitPendingUnhandledRejections();
} while (tickInfo[kLength] !== 0);
} while (nextTickQueue.head !== null);
}

class TickObject {
constructor(callback, args, asyncId, triggerAsyncId) {
constructor(callback, args, triggerAsyncId) {
// this must be set to null first to avoid function tracking
// on the hidden class, revisit in V8 versions after 6.2
this.callback = null;
this.callback = callback;
this.args = args;

const asyncId = ++async_id_fields[kAsyncIdCounter];
this[async_id_symbol] = asyncId;
this[trigger_async_id_symbol] = triggerAsyncId;

Expand Down Expand Up @@ -203,13 +164,7 @@ function setupNextTick() {
args[i - 1] = arguments[i];
}

// In V8 6.2, moving tickInfo & async_id_fields[kAsyncIdCounter] into the
// TickObject incurs a significant performance penalty in the
// next-tick-breadth-args benchmark (revisit later)
++tickInfo[kLength];
nextTickQueue.push(new TickObject(callback,
args,
++async_id_fields[kAsyncIdCounter],
nextTickQueue.push(new TickObject(callback, args,
getDefaultTriggerAsyncId()));
}

Expand Down Expand Up @@ -238,13 +193,6 @@ function setupNextTick() {

if (triggerAsyncId === null)
triggerAsyncId = getDefaultTriggerAsyncId();
// In V8 6.2, moving tickInfo & async_id_fields[kAsyncIdCounter] into the
// TickObject incurs a significant performance penalty in the
// next-tick-breadth-args benchmark (revisit later)
++tickInfo[kLength];
nextTickQueue.push(new TickObject(callback,
args,
++async_id_fields[kAsyncIdCounter],
triggerAsyncId));
nextTickQueue.push(new TickObject(callback, args, triggerAsyncId));
}
}
Loading