Skip to content

Commit

Permalink
src: remove Async Listener
Browse files Browse the repository at this point in the history
Async Listener was the name of the user-facing JS API, and is being
completely removed. Instead low level hooks directly into the mechanism
that AL used will be introduced in a future commit.

PR-URL: nodejs/node-v0.x-archive#8110
Signed-off-by: Trevor Norris <[email protected]>
Reviewed-by: Fedor Indutny <[email protected]>
Reviewed-by: Alexis Campailla <[email protected]>
Reviewed-by: Julien Gilli <[email protected]>
  • Loading branch information
trevnorris authored and piscisaureus committed Dec 9, 2014
1 parent 4d94658 commit 0d60ab3
Show file tree
Hide file tree
Showing 28 changed files with 8 additions and 2,371 deletions.
224 changes: 1 addition & 223 deletions doc/api/tracing.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@
The tracing module is designed for instrumenting your Node application. It is
not meant for general purpose use.

***Be very careful with callbacks used in conjunction with this module***

Many of these callbacks interact directly with asynchronous subsystems in a
synchronous fashion. That is to say, you may be in a callback where a call to
`console.log()` could result in an infinite recursive loop. Also of note, many
of these callbacks are in hot execution code paths. That is to say your
callbacks are executed quite often in the normal operation of Node, so be wary
of doing CPU bound or synchronous workloads in these functions. Consider a ring
buffer and a timer to defer processing.

`require('tracing')` to use this module.

## v8
Expand Down Expand Up @@ -73,216 +63,4 @@ v8.setFlagsFromString('--trace_gc');
setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
```


# Async Listeners

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 may have a
**significant performance impact** on your application.


## tracing.createAsyncListener(callbacksObj[, userData])

* `callbacksObj` {Object} Contains optional callbacks that will fire at
specific times in the life cycle of the asynchronous event.
* `userData` {Value} a value that will be passed to all callbacks.

Returns a constructed `AsyncListener` object.

To begin capturing asynchronous events pass either the `callbacksObj` or
pass an existing `AsyncListener` instance to [`tracing.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 `AsyncListener` instance to
[`tracing.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:


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

* `create(userData)`: 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
`tracing.createAsyncListener()`'s `userData` argument. If an initial
`userData` was passed when created, then `create()` will
receive that as a function argument.

* `before(context, userData)`: 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 `userData`
either returned from `create()` or passed during construction (if
either occurred).

* `after(context, userData)`: 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(userData, error)`: A `Function` called if the event's
callback threw. If this registered callback 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, but all `error()` callbacks will always be run.

`userData`: 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 `create()`.

Here is an example of overwriting the `userData`:

tracing.createAsyncListener({
create: 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 `create()` will not fire when
an event is added, and `before()`/`after()` will not fire when emitted
callbacks are called.


## tracing.addAsyncListener(callbacksObj[, userData])
## tracing.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
[`tracing.createAsyncListener()`][], or a constructed `AsyncListener`
object.

Example usage for capturing errors:

var fs = require('fs');

var cntr = 0;
var key = tracing.addAsyncListener({
create: function onCreate() {
return { uid: cntr++ };
},
before: function onBefore(context, storage) {
// Write directly to stdout or we'll enter a recursive loop
fs.writeSync(1, 'uid: ' + storage.uid + ' is about to run\n');
},
after: function onAfter(context, storage) {
fs.writeSync(1, 'uid: ' + storage.uid + ' ran\n');
},
error: function onError(storage, err) {
// Handle known errors
if (err.message === 'everything is fine') {
// Writing to stderr this time.
fs.writeSync(2, 'handled error just threw:\n');
fs.writeSync(2, err.stack + '\n');
return true;
}
}
});

process.nextTick(function() {
throw new Error('everything is fine');
});

// 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

## tracing.removeAsyncListener(asyncListener)

Removes the `AsyncListener` from the listening queue.

Removing the `AsyncListener` from the active queue does _not_ mean the
`asyncListener` callbacks will cease to fire on the events they've been
registered. Subsequently, any asynchronous events fired during the
execution of a callback will also have the same `asyncListener` callbacks
attached for future execution. For example:

var fs = require('fs');

var key = tracing.createAsyncListener({
create: function asyncListener() {
// Write directly to stdout or we'll enter a recursive loop
fs.writeSync(1, 'You summoned me?\n');
}
});

// We want to begin capturing async events some time in the future.
setTimeout(function() {
tracing.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.
tracing.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
`tracing.removeAsyncListener()` must be called from within the call
stack itself. For example:

var fs = require('fs');

var key = tracing.createAsyncListener({
create: function asyncListener() {
// Write directly to stdout or we'll enter a recursive loop
fs.writeSync(1, 'You summoned me?\n');
}
});

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

// Perform a few additional async events.
setImmediate(function() {
// Stop capturing from this call stack.
tracing.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
[`tracing.createAsyncListener()`]: #tracing_tracing_createasynclistener_asynclistener_callbacksobj_storagevalue
[`tracing.addAsyncListener()`]: #tracing_tracing_addasynclistener_asynclistener
[`tracing.removeAsyncListener()`]: #tracing_tracing_removeasynclistener_asynclistener

53 changes: 3 additions & 50 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,6 @@ var TIMEOUT_MAX = 2147483647; // 2^31-1

var debug = require('util').debuglog('timer');

var tracing = require('tracing');
var asyncFlags = tracing._asyncFlags;
var runAsyncQueue = tracing._runAsyncQueue;
var loadAsyncQueue = tracing._loadAsyncQueue;
var unloadAsyncQueue = tracing._unloadAsyncQueue;

// Same as in AsyncListener in env.h
var kHasListener = 0;

// Do a little housekeeping.
delete tracing._asyncFlags;
delete tracing._runAsyncQueue;
delete tracing._loadAsyncQueue;
delete tracing._unloadAsyncQueue;


// IDLE TIMEOUTS
//
Expand All @@ -61,11 +46,6 @@ delete tracing._unloadAsyncQueue;
// value = list
var lists = {};

// Make Timer as monomorphic as possible.
Timer.prototype._asyncQueue = undefined;
Timer.prototype._asyncData = undefined;
Timer.prototype._asyncFlags = 0;

// the main function - creates lists on demand and the watchers associated
// with them.
function insert(item, msecs) {
Expand Down Expand Up @@ -102,7 +82,7 @@ function listOnTimeout() {
var now = Timer.now();
debug('now: %s', now);

var diff, first, hasQueue, threw;
var diff, first, threw;
while (first = L.peek(list)) {
diff = now - first._idleStart;
if (diff < msecs) {
Expand All @@ -124,19 +104,13 @@ function listOnTimeout() {
if (domain && domain._disposed)
continue;

hasQueue = !!first._asyncQueue;

try {
if (hasQueue)
loadAsyncQueue(first);
if (domain)
domain.enter();
threw = true;
first._onTimeout();
if (domain)
domain.exit();
if (hasQueue)
unloadAsyncQueue(first);
threw = false;
} finally {
if (threw) {
Expand Down Expand Up @@ -206,11 +180,6 @@ exports.active = function(item) {
L.append(list, item);
}
}
// Whether or not a new TimerWrap needed to be created, this should run
// for each item. This way each "item" (i.e. timer) can properly have
// their own domain assigned.
if (asyncFlags[kHasListener] > 0)
runAsyncQueue(item);
};


Expand Down Expand Up @@ -356,18 +325,15 @@ L.init(immediateQueue);

function processImmediate() {
var queue = immediateQueue;
var domain, hasQueue, immediate;
var domain, immediate;

immediateQueue = {};
L.init(immediateQueue);

while (L.isEmpty(queue) === false) {
immediate = L.shift(queue);
hasQueue = !!immediate._asyncQueue;
domain = immediate.domain;

if (hasQueue)
loadAsyncQueue(immediate);
if (domain)
domain.enter();

Expand All @@ -391,8 +357,6 @@ function processImmediate() {

if (domain)
domain.exit();
if (hasQueue)
unloadAsyncQueue(immediate);
}

// Only round-trip to C++ land if we have to. Calling clearImmediate() on an
Expand All @@ -408,11 +372,8 @@ function Immediate() { }

Immediate.prototype.domain = undefined;
Immediate.prototype._onImmediate = undefined;
Immediate.prototype._asyncQueue = undefined;
Immediate.prototype._asyncData = undefined;
Immediate.prototype._idleNext = undefined;
Immediate.prototype._idlePrev = undefined;
Immediate.prototype._asyncFlags = 0;


exports.setImmediate = function(callback) {
Expand All @@ -438,9 +399,6 @@ exports.setImmediate = function(callback) {
process._immediateCallback = processImmediate;
}

// setImmediates are handled more like nextTicks.
if (asyncFlags[kHasListener] > 0)
runAsyncQueue(immediate);
if (process.domain)
immediate.domain = process.domain;

Expand Down Expand Up @@ -474,7 +432,7 @@ function unrefTimeout() {

debug('unrefTimer fired');

var diff, domain, first, hasQueue, threw;
var diff, domain, first, threw;
while (first = L.peek(unrefList)) {
diff = now - first._idleStart;

Expand All @@ -492,20 +450,15 @@ function unrefTimeout() {

if (!first._onTimeout) continue;
if (domain && domain._disposed) continue;
hasQueue = !!first._asyncQueue;

try {
if (hasQueue)
loadAsyncQueue(first);
if (domain) domain.enter();
threw = true;
debug('unreftimer firing timeout');
first._onTimeout();
threw = false;
if (domain)
domain.exit();
if (hasQueue)
unloadAsyncQueue(first);
} finally {
if (threw) process.nextTick(unrefTimeout);
}
Expand Down
Loading

0 comments on commit 0d60ab3

Please sign in to comment.