Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

implement domain-like hooks (asynclistener) for userland #6011

Closed
wants to merge 9 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
4 changes: 2 additions & 2 deletions doc/api/net.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ already been bound to a port or domain socket.
Listening on a file descriptor is not supported on Windows.

This function is asynchronous. When the server has been bound,
['listening'](#event_listening_) event will be emitted.
['listening'][] event will be emitted.
the last parameter `callback` will be added as an listener for the
['listening'](#event_listening_) event.
['listening'][] event.

### server.close([callback])

Expand Down
214 changes: 214 additions & 0 deletions doc/api/process.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -647,4 +647,218 @@ a diff reading, useful for benchmarks and measuring intervals:
// benchmark took 1000000527 nanoseconds
}, 1000);


## Async Listeners

<!-- type=misc -->

Stability: 1 - Experimental

The `AsyncListener` API is the JavaScript interface for the `AsyncWrap`
class which allows developers to be notified about key events in the
lifetime of an asynchronous event. Node performs a lot of asynchronous
events internally, and significant use of this API will have a **dramatic
performance impact** on your application.


## process.createAsyncListener(asyncListener[, callbacksObj[, storageValue]])

* `asyncListener` {Function} callback fired when an asynchronous event is
instantiated.
* `callbacksObj` {Object} optional callbacks that will fire at specific
times in the lifetime of the asynchronous event.
* `storageValue` {Value} a value that will be passed as the first argument
when the `asyncListener` callback is run, and to all subsequent callback.

Returns a constructed `AsyncListener` object.

To begin capturing asynchronous events pass the object to
[`process.addAsyncListener()`][]. The same `AsyncListener` instance can
only be added once to the active queue, and subsequent attempts to add the
instance will be ignored.

To stop capturing pass the object to [`process.removeAsyncListener()`][].
This does _not_ mean the `AsyncListener` previously added will stop
triggering callbacks. Once attached to an asynchronous event it will
persist with the lifetime of the asynchronous call stack.

Explanation of function parameters:

`asyncListener(storageValue)`: A `Function` called when an asynchronous
event is instantiated. If a `Value` is returned then it will be attached
to the event and overwrite any value that had been passed to
`process.createAsyncListener()`'s `storageValue` argument. If an initial
`storageValue` was passed when created, then `asyncListener()` will
receive that as a function argument.

`callbacksObj`: An `Object` which may contain three optional fields:

* `before(context, storageValue)`: A `Function` that is called immediately
before the asynchronous callback is about to run. It will be passed both
the `context` (i.e. `this`) of the calling function and the `storageValue`
either returned from `asyncListener` or passed during construction (if
either occurred).

* `after(context, storageValue)`: A `Function` called immediately after
the asynchronous event's callback has run. Note this will not be called
if the callback throws and the error is not handled.

* `error(storageValue, error)`: A `Function` called if the event's
callback threw. If `error` returns `true` then Node will assume the error
has been properly handled and resume execution normally. When multiple
`error()` callbacks have been registered, only **one** of those callbacks
needs to return `true` for `AsyncListener` to accept that the error has
been handled.

`storageValue`: A `Value` (i.e. anything) that will be, by default,
attached to all new event instances. This will be overwritten if a `Value`
is returned by `asyncListener()`.

Here is an example of overwriting the `storageValue`:

process.createAsyncListener(function listener(value) {
// value === true
return false;
}, {
before: function before(context, value) {
// value === false
}
}, true);

**Note:** The [EventEmitter][], while used to emit status of an asynchronous
event, is not itself asynchronous. So `asyncListener()` will not fire when
an event is added, and `before`/`after` will not fire when emitted
callbacks are called.


## process.addAsyncListener(asyncListener[, callbacksObj[, storageValue]])
## process.addAsyncListener(asyncListener)

Returns a constructed `AsyncListener` object and immediately adds it to
the listening queue to begin capturing asynchronous events.

Function parameters can either be the same as
[`process.createAsyncListener()`][], or a constructed `AsyncListener`
object.

Example usage for capturing errors:

var cntr = 0;
var key = process.addAsyncListener(function() {
return { uid: cntr++ };
}, {
before: function onBefore(context, storage) {
// Need to remove the listener while logging or will end up
// with an infinite call loop.
process.removeAsyncListener(key);
console.log('uid: %s is about to run', storage.uid);
process.addAsyncListener(key);
},
after: function onAfter(context, storage) {
process.removeAsyncListener(key);
console.log('uid: %s is about to run', storage.uid);
process.addAsyncListener(key);
},
error: function onError(storage, err) {
// Handle known errors
if (err.message === 'really, it\'s ok') {
process.removeAsyncListener(key);
console.log('handled error just threw:');
console.log(err.stack);
process.addAsyncListener(key);
return true;
}
}
});

process.nextTick(function() {
throw new Error('really, it\'s ok');
});

// Output:
// uid: 0 is about to run
// handled error just threw:
// Error: really, it's ok
// at /tmp/test2.js:27:9
// at process._tickCallback (node.js:583:11)
// at Function.Module.runMain (module.js:492:11)
// at startup (node.js:123:16)
// at node.js:1012:3

## process.removeAsyncListener(asyncListener)

Removes the `AsyncListener` from the listening queue.

Removing the `AsyncListener` from the queue does _not_ mean asynchronous
events called during its execution scope will stop firing callbacks. Once
attached to an event it will persist for the entire asynchronous call
stack. For example:

var key = process.createAsyncListener(function asyncListener() {
// To log we must stop listening or we'll enter infinite recursion.
process.removeAsyncListener(key);
console.log('You summoned me?');
process.addAsyncListener(key);
});

// We want to begin capturing async events some time in the future.
setTimeout(function() {
process.addAsyncListener(key);

// Perform a few additional async events.
setTimeout(function() {
setImmediate(function() {
process.nextTick(function() { });
});
});

// Removing the listener doesn't mean to stop capturing events that
// have already been added.
process.removeAsyncListener(key);
}, 100);

// Output:
// You summoned me?
// You summoned me?
// You summoned me?
// You summoned me?

The fact that we logged 4 asynchronous events is an implementation detail
of Node's [Timers][].

To stop capturing from a specific asynchronous event stack
`process.removeAsyncListener()` must be called from within the call
stack itself. For example:

var key = process.createAsyncListener(function asyncListener() {
// To log we must stop listening or we'll enter infinite recursion.
process.removeAsyncListener(key);
console.log('You summoned me?');
process.addAsyncListener(key);
});

// We want to begin capturing async events some time in the future.
setTimeout(function() {
process.addAsyncListener(key);

// Perform a few additional async events.
setImmediate(function() {
// Stop capturing from this call stack.
process.removeAsyncListener(key);

process.nextTick(function() { });
});
}, 100);

// Output:
// You summoned me?

The user must be explicit and always pass the `AsyncListener` they wish
to remove. It is not possible to simply remove all listeners at once.


[EventEmitter]: events.html#events_class_events_eventemitter
[Timers]: timers.html
[`process.createAsyncListener()`]: #process_process_createasynclistener_asynclistener_callbacksobj_storagevalue
[`process.addAsyncListener()`]: #process_process_addasynclistener_asynclistener
[`process.removeAsyncListener()`]: #process_process_removeasynclistener_asynclistener
63 changes: 35 additions & 28 deletions lib/_http_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,38 +417,45 @@ function responseOnEnd() {
}
}

function tickOnSocket(req, socket) {
var parser = parsers.alloc();
req.socket = socket;
req.connection = socket;
parser.reinitialize(HTTPParser.RESPONSE);
parser.socket = socket;
parser.incoming = null;
req.parser = parser;

socket.parser = parser;
socket._httpMessage = req;

// Setup "drain" propogation.
httpSocketSetup(socket);

// Propagate headers limit from request object to parser
if (util.isNumber(req.maxHeadersCount)) {
parser.maxHeaderPairs = req.maxHeadersCount << 1;
} else {
// Set default value because parser may be reused from FreeList
parser.maxHeaderPairs = 2000;
}

parser.onIncoming = parserOnIncomingClient;
socket.on('error', socketErrorListener);
socket.on('data', socketOnData);
socket.on('end', socketOnEnd);
socket.on('close', socketCloseListener);
req.emit('socket', socket);
}

ClientRequest.prototype.onSocket = function(socket) {
var req = this;

process.nextTick(function() {
var parser = parsers.alloc();
req.socket = socket;
req.connection = socket;
parser.reinitialize(HTTPParser.RESPONSE);
parser.socket = socket;
parser.incoming = null;
req.parser = parser;

socket.parser = parser;
socket._httpMessage = req;

// Setup "drain" propogation.
httpSocketSetup(socket);

// Propagate headers limit from request object to parser
if (util.isNumber(req.maxHeadersCount)) {
parser.maxHeaderPairs = req.maxHeadersCount << 1;
} else {
// Set default value because parser may be reused from FreeList
parser.maxHeaderPairs = 2000;
}

parser.onIncoming = parserOnIncomingClient;
socket.on('error', socketErrorListener);
socket.on('data', socketOnData);
socket.on('end', socketOnEnd);
socket.on('close', socketCloseListener);
req.emit('socket', socket);
// If a domain was added to the request, attach it to the socket.
if (req.domain)
socket._handle.addAsyncListener(req.domain._listener);
tickOnSocket(req, socket);
});

};
Expand Down
Loading