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] 17738 and 17841 #18488

Closed
wants to merge 2 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
45 changes: 20 additions & 25 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@
}
}

function noop() {}

function setupProcessFatal() {
const async_wrap = process.binding('async_wrap');
// Arrays containing hook flags and ids for async_hook calls.
Expand All @@ -371,23 +373,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 @@ -396,24 +390,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
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 kScheduled = 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[kScheduled] = 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[kScheduled] = 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));
}
}
14 changes: 3 additions & 11 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,24 +222,16 @@ inline Environment::TickInfo::TickInfo() {
fields_[i] = 0;
}

inline uint32_t* Environment::TickInfo::fields() {
inline uint8_t* Environment::TickInfo::fields() {
return fields_;
}

inline int Environment::TickInfo::fields_count() const {
return kFieldsCount;
}

inline uint32_t Environment::TickInfo::index() const {
return fields_[kIndex];
}

inline uint32_t Environment::TickInfo::length() const {
return fields_[kLength];
}

inline void Environment::TickInfo::set_index(uint32_t value) {
fields_[kIndex] = value;
inline uint8_t Environment::TickInfo::scheduled() const {
return fields_[kScheduled];
}

inline void Environment::AssignToContext(v8::Local<v8::Context> context,
Expand Down
11 changes: 4 additions & 7 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -455,23 +455,20 @@ class Environment {

class TickInfo {
public:
inline uint32_t* fields();
inline uint8_t* fields();
inline int fields_count() const;
inline uint32_t index() const;
inline uint32_t length() const;
inline void set_index(uint32_t value);
inline uint8_t scheduled() const;

private:
friend class Environment; // So we can call the constructor.
inline TickInfo();

enum Fields {
kIndex,
kLength,
kScheduled,
kFieldsCount
};

uint32_t fields_[kFieldsCount];
uint8_t fields_[kFieldsCount];

DISALLOW_COPY_AND_ASSIGN(TickInfo);
};
Expand Down
Loading