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

Backport async hooks to v8.x #18179

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7a5e327
src: explicitly register built-in modules
yhwang Oct 22, 2017
6e98aa9
src: use unique pointer for tracing_agent
fhinkel Nov 12, 2017
2bffd70
async_hooks: add trace events to async_hooks
AndreasMadsen Jul 17, 2017
07e7f2e
async_hooks: add destroy event for gced AsyncResources
Sebmaster Nov 9, 2017
1a66f91
src: use NODE_BUILTIN_MODULE_CONTEXT_AWARE() macro
bnoordhuis Nov 16, 2017
b7e3109
src: rename async-wrap -> async_wrap
danbev Nov 14, 2017
8c83406
timers: cross JS/C++ border less frequently
addaleax Nov 15, 2017
d66d481
async_hooks: deprecate undocumented API
AndreasMadsen Nov 12, 2017
c904ce1
src: introduce internal C++ SetImmediate() mechanism
addaleax Nov 18, 2017
590cf4a
src: remove async_hooks destroy timer handle
addaleax Nov 18, 2017
d32d180
trace_events: add executionAsyncId to init events
AndreasMadsen Nov 24, 2017
44cbf56
async_wrap: add provider types for net server
AndreasMadsen Nov 20, 2017
1044e76
async_hooks: rename initTriggerId
AndreasMadsen Nov 22, 2017
a04d5ee
async_hooks: separate missing from default context
AndreasMadsen Nov 22, 2017
44f4c73
async_hooks: use scope for defaultTriggerAsyncId
AndreasMadsen Nov 22, 2017
7310f66
src: remove unused async hooks methods
addaleax Dec 19, 2017
4e656fd
async_hooks: use CHECK instead of throwing error
maclover7 Dec 23, 2017
067bd2a
async_hooks: use typed array stack as fast path
addaleax Dec 19, 2017
af928ef
trace_events: stop tracing agent in process.exit()
AndreasMadsen Jan 5, 2018
1efac0e
async_hooks: update defaultTriggerAsyncIdScope for perf
apapirovski Jan 5, 2018
3ba594f
async_hooks,http: set HTTPParser trigger to socket
AndreasMadsen Jan 5, 2018
8937440
async_hooks,test: only use IPv6 in http test
AndreasMadsen Jan 14, 2018
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: 45 additions & 0 deletions benchmark/async_hooks/gc-tracking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';
const common = require('../common.js');
const { AsyncResource } = require('async_hooks');

const bench = common.createBenchmark(main, {
n: [1e6],
method: [
'trackingEnabled',
'trackingDisabled',
]
}, {
flags: ['--expose-gc']
});

function endAfterGC(n) {
setImmediate(() => {
global.gc();
setImmediate(() => {
bench.end(n);
});
});
}

function main(conf) {
const n = +conf.n;

switch (conf.method) {
case 'trackingEnabled':
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar');
}
endAfterGC(n);
break;
case 'trackingDisabled':
bench.start();
for (let i = 0; i < n; i++) {
new AsyncResource('foobar', { requireManualDestroy: true });
}
endAfterGC(n);
break;
default:
throw new Error('Unsupported method');
}
}
6 changes: 3 additions & 3 deletions benchmark/net/tcp-raw-c2s.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const bench = common.createBenchmark(main, {
dur: [5]
});

const TCP = process.binding('tcp_wrap').TCP;
const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const TCPConnectWrap = process.binding('tcp_wrap').TCPConnectWrap;
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const PORT = common.PORT;
Expand All @@ -36,7 +36,7 @@ function fail(err, syscall) {
}

function server() {
const serverHandle = new TCP();
const serverHandle = new TCP(TCPConstants.SERVER);
var err = serverHandle.bind('127.0.0.1', PORT);
if (err)
fail(err, 'bind');
Expand Down Expand Up @@ -92,7 +92,7 @@ function client() {
throw new Error(`invalid type: ${type}`);
}

const clientHandle = new TCP();
const clientHandle = new TCP(TCPConstants.SOCKET);
const connectReq = new TCPConnectWrap();
const err = clientHandle.connect(connectReq, '127.0.0.1', PORT);

Expand Down
6 changes: 3 additions & 3 deletions benchmark/net/tcp-raw-pipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const bench = common.createBenchmark(main, {
dur: [5]
});

const TCP = process.binding('tcp_wrap').TCP;
const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const TCPConnectWrap = process.binding('tcp_wrap').TCPConnectWrap;
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const PORT = common.PORT;
Expand All @@ -35,7 +35,7 @@ function fail(err, syscall) {
}

function server() {
const serverHandle = new TCP();
const serverHandle = new TCP(TCPConstants.SERVER);
var err = serverHandle.bind('127.0.0.1', PORT);
if (err)
fail(err, 'bind');
Expand Down Expand Up @@ -89,7 +89,7 @@ function client() {
throw new Error(`invalid type: ${type}`);
}

const clientHandle = new TCP();
const clientHandle = new TCP(TCPConstants.SOCKET);
const connectReq = new TCPConnectWrap();
const err = clientHandle.connect(connectReq, '127.0.0.1', PORT);
var bytes = 0;
Expand Down
6 changes: 3 additions & 3 deletions benchmark/net/tcp-raw-s2c.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const bench = common.createBenchmark(main, {
dur: [5]
});

const TCP = process.binding('tcp_wrap').TCP;
const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const TCPConnectWrap = process.binding('tcp_wrap').TCPConnectWrap;
const WriteWrap = process.binding('stream_wrap').WriteWrap;
const PORT = common.PORT;
Expand All @@ -35,7 +35,7 @@ function fail(err, syscall) {
}

function server() {
const serverHandle = new TCP();
const serverHandle = new TCP(TCPConstants.SERVER);
var err = serverHandle.bind('127.0.0.1', PORT);
if (err)
fail(err, 'bind');
Expand Down Expand Up @@ -107,7 +107,7 @@ function server() {
}

function client() {
const clientHandle = new TCP();
const clientHandle = new TCP(TCPConstants.SOCKET);
const connectReq = new TCPConnectWrap();
const err = clientHandle.connect(connectReq, '127.0.0.1', PORT);

Expand Down
30 changes: 19 additions & 11 deletions doc/api/async_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ resource's constructor.
```text
FSEVENTWRAP, FSREQWRAP, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPPARSER,
JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP, SHUTDOWNWRAP,
SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPWRAP, TIMERWRAP, TTYWRAP,
SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVER, TCPWRAP, TIMERWRAP, TTYWRAP,
UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
RANDOMBYTESREQUEST, TLSWRAP, Timeout, Immediate, TickObject
```
Expand Down Expand Up @@ -275,13 +275,13 @@ require('net').createServer((conn) => {}).listen(8080);
Output when hitting the server with `nc localhost 8080`:

```console
TCPWRAP(2): trigger: 1 execution: 1
TCPSERVERWRAP(2): trigger: 1 execution: 1
TCPWRAP(4): trigger: 2 execution: 0
```

The first `TCPWRAP` is the server which receives the connections.
The `TCPSERVERWRAP` is the server which receives the connections.

The second `TCPWRAP` is the new connection from the client. When a new
The `TCPWRAP` is the new connection from the client. When a new
connection is made the `TCPWrap` instance is immediately constructed. This
happens outside of any JavaScript stack (side note: a `executionAsyncId()` of `0`
means it's being executed from C++, with no JavaScript stack above it).
Expand Down Expand Up @@ -354,7 +354,7 @@ require('net').createServer(() => {}).listen(8080, () => {
Output from only starting the server:

```console
TCPWRAP(2): trigger: 1 execution: 1
TCPSERVERWRAP(2): trigger: 1 execution: 1
TickObject(3): trigger: 2 execution: 1
before: 3
Timeout(4): trigger: 3 execution: 3
Expand Down Expand Up @@ -387,7 +387,7 @@ Only using `execution` to graph resource allocation results in the following:
TTYWRAP(6) -> Timeout(4) -> TIMERWRAP(5) -> TickObject(3) -> root(1)
```

The `TCPWRAP` is not part of this graph, even though it was the reason for
The `TCPSERVERWRAP` is not part of this graph, even though it was the reason for
`console.log()` being called. This is because binding to a port without a
hostname is a *synchronous* operation, but to maintain a completely asynchronous
API the user's callback is placed in a `process.nextTick()`.
Expand Down Expand Up @@ -545,12 +545,14 @@ will occur and the process will abort.
The following is an overview of the `AsyncResource` API.

```js
const { AsyncResource } = require('async_hooks');
const { AsyncResource, executionAsyncId } = require('async_hooks');

// AsyncResource() is meant to be extended. Instantiating a
// new AsyncResource() also triggers init. If triggerAsyncId is omitted then
// async_hook.executionAsyncId() is used.
const asyncResource = new AsyncResource(type, triggerAsyncId);
const asyncResource = new AsyncResource(
type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false }
);

// Call AsyncHooks before callbacks.
asyncResource.emitBefore();
Expand All @@ -568,11 +570,17 @@ asyncResource.asyncId();
asyncResource.triggerAsyncId();
```

#### `AsyncResource(type[, triggerAsyncId])`
#### `AsyncResource(type[, options])`

* `type` {string} The type of async event.
* `triggerAsyncId` {number} The ID of the execution context that created this
async event.
* `options` {Object}
* `triggerAsyncId` {number} The ID of the execution context that created this
async event. **Default:** `executionAsyncId()`
* `requireManualDestroy` {boolean} Disables automatic `emitDestroy` when the
object is garbage collected. This usually does not need to be set (even if
`emitDestroy` is called manually), unless the resource's asyncId is retrieved
and the sensitive API's `emitDestroy` is called with it.
**Default:** `false`

Example usage:

Expand Down
19 changes: 19 additions & 0 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,25 @@ function for [`util.inspect()`][] is deprecated. Use [`util.inspect.custom`][]
instead. For backwards compatibility with Node.js prior to version 6.4.0, both
may be specified.

<a id="DEP0085"></a>
### DEP0085: AsyncHooks Sensitive API

Type: Runtime

The AsyncHooks Sensitive API was never documented and had various of minor
issues, see https://github.com/nodejs/node/issues/15572. Use the `AsyncResource`
API instead.


<a id="DEP0086"></a>
### DEP0086: Remove runInAsyncIdScope

Type: Runtime

`runInAsyncIdScope` doesn't emit the `before` or `after` event and can thus
cause a lot of issues. See https://github.com/nodejs/node/issues/14328 for more
details.

[`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size
[`Buffer.from(array)`]: buffer.html#buffer_class_method_buffer_from_array
[`Buffer.from(buffer)`]: buffer.html#buffer_class_method_buffer_from_buffer
Expand Down
4 changes: 2 additions & 2 deletions doc/api/tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ Node.js application.

The set of categories for which traces are recorded can be specified using the
`--trace-event-categories` flag followed by a list of comma separated category names.
By default the `node` and `v8` categories are enabled.
By default the `node`, `node.async_hooks`, and `v8` categories are enabled.

```txt
node --trace-events-enabled --trace-event-categories v8,node server.js
node --trace-events-enabled --trace-event-categories v8,node,node.async_hooks server.js
```

Running Node.js with tracing enabled will produce log files that can be opened
Expand Down
2 changes: 1 addition & 1 deletion lib/_http_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ function responseKeepAlive(res, req) {
socket.removeListener('error', socketErrorListener);
socket.once('error', freeSocketErrorListener);
// There are cases where _handle === null. Avoid those. Passing null to
// nextTick() will call initTriggerId() to retrieve the id.
// nextTick() will call getDefaultTriggerAsyncId() to retrieve the id.
const asyncId = socket._handle ? socket._handle.getAsyncId() : null;
// Mark this socket as available, AFTER user-added end
// handlers have a chance to run.
Expand Down
3 changes: 1 addition & 2 deletions lib/_http_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const { methods, HTTPParser } = process.binding('http_parser');
const FreeList = require('internal/freelist');
const { ondrain } = require('internal/http');
const incoming = require('_http_incoming');
const { emitDestroy } = require('async_hooks');
const {
IncomingMessage,
readStart,
Expand Down Expand Up @@ -217,7 +216,7 @@ function freeParser(parser, req, socket) {
} else {
// Since the Parser destructor isn't going to run the destroy() callbacks
// it needs to be triggered manually.
emitDestroy(parser.getAsyncId());
parser.free();
}
}
if (req) {
Expand Down
28 changes: 19 additions & 9 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const {
} = require('_http_common');
const { OutgoingMessage } = require('_http_outgoing');
const { outHeadersKey, ondrain } = require('internal/http');
const {
defaultTriggerAsyncIdScope,
getOrSetAsyncId
} = require('internal/async_hooks');

const STATUS_CODES = {
100: 'Continue',
Expand Down Expand Up @@ -289,20 +293,26 @@ Server.prototype.setTimeout = function setTimeout(msecs, callback) {


function connectionListener(socket) {
defaultTriggerAsyncIdScope(
getOrSetAsyncId(socket), connectionListenerInternal, this, socket
);
}

function connectionListenerInternal(server, socket) {
debug('SERVER new http connection');

httpSocketSetup(socket);

// Ensure that the server property of the socket is correctly set.
// See https://github.com/nodejs/node/issues/13435
if (socket.server === null)
socket.server = this;
socket.server = server;

// If the user has added a listener to the server,
// request, or response, then it's their responsibility.
// otherwise, destroy on timeout by default
if (this.timeout && typeof socket.setTimeout === 'function')
socket.setTimeout(this.timeout);
if (server.timeout && typeof socket.setTimeout === 'function')
socket.setTimeout(server.timeout);
socket.on('timeout', socketOnTimeout);

var parser = parsers.alloc();
Expand All @@ -312,8 +322,8 @@ function connectionListener(socket) {
parser.incoming = null;

// Propagate headers limit from server instance to parser
if (typeof this.maxHeadersCount === 'number') {
parser.maxHeaderPairs = this.maxHeadersCount << 1;
if (typeof server.maxHeadersCount === 'number') {
parser.maxHeaderPairs = server.maxHeadersCount << 1;
} else {
// Set default value because parser may be reused from FreeList
parser.maxHeaderPairs = 2000;
Expand All @@ -333,16 +343,16 @@ function connectionListener(socket) {
outgoingData: 0,
keepAliveTimeoutSet: false
};
state.onData = socketOnData.bind(undefined, this, socket, parser, state);
state.onEnd = socketOnEnd.bind(undefined, this, socket, parser, state);
state.onData = socketOnData.bind(undefined, server, socket, parser, state);
state.onEnd = socketOnEnd.bind(undefined, server, socket, parser, state);
state.onClose = socketOnClose.bind(undefined, socket, state);
state.onDrain = socketOnDrain.bind(undefined, socket, state);
socket.on('data', state.onData);
socket.on('error', socketOnError);
socket.on('end', state.onEnd);
socket.on('close', state.onClose);
socket.on('drain', state.onDrain);
parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state);
parser.onIncoming = parserOnIncoming.bind(undefined, server, socket, state);

// We are consuming socket, so it won't get any actual data
socket.on('resume', onSocketResume);
Expand All @@ -361,7 +371,7 @@ function connectionListener(socket) {
}
}
parser[kOnExecute] =
onParserExecute.bind(undefined, this, socket, parser, state);
onParserExecute.bind(undefined, server, socket, parser, state);

socket._paused = false;
}
Expand Down
8 changes: 5 additions & 3 deletions lib/_tls_wrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const { Buffer } = require('buffer');
const debug = util.debuglog('tls');
const { Timer } = process.binding('timer_wrap');
const tls_wrap = process.binding('tls_wrap');
const { TCP } = process.binding('tcp_wrap');
const { Pipe } = process.binding('pipe_wrap');
const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const { Pipe, constants: PipeConstants } = process.binding('pipe_wrap');
const kDisableRenegotiation = Symbol('disable-renegotiation');

function onhandshakestart() {
Expand Down Expand Up @@ -376,7 +376,9 @@ TLSSocket.prototype._wrapHandle = function(wrap) {

var options = this._tlsOptions;
if (!handle) {
handle = options.pipe ? new Pipe() : new TCP();
handle = options.pipe ?
new Pipe(PipeConstants.SOCKET) :
new TCP(TCPConstants.SOCKET);
handle.owner = this;
}

Expand Down
Loading