Skip to content

Commit d9fc6af

Browse files
committed
node: change AsyncListener API
There was a flaw in the old API that has been fixed. Now the asyncListener callback is now the "create" object property in the callback object, and is optional.
1 parent 13eb17f commit d9fc6af

22 files changed

Lines changed: 137 additions & 142 deletions

doc/api/process.markdown

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -688,81 +688,81 @@ a diff reading, useful for benchmarks and measuring intervals:
688688
The `AsyncListener` API is the JavaScript interface for the `AsyncWrap`
689689
class which allows developers to be notified about key events in the
690690
lifetime of an asynchronous event. Node performs a lot of asynchronous
691-
events internally, and significant use of this API will have a **dramatic
692-
performance impact** on your application.
691+
events internally, and significant use of this API may have a
692+
**significant performance impact** on your application.
693693

694694

695-
## process.createAsyncListener(asyncListener[, callbacksObj[, storageValue]])
695+
## process.createAsyncListener(callbacksObj[, userData])
696696

697-
* `asyncListener` {Function} callback fired when an asynchronous event is
698-
instantiated.
699-
* `callbacksObj` {Object} optional callbacks that will fire at specific
700-
times in the lifetime of the asynchronous event.
701-
* `storageValue` {Value} a value that will be passed as the first argument
702-
when the `asyncListener` callback is run, and to all subsequent callback.
697+
* `callbacksObj` {Object} Contains optional callbacks that will fire at
698+
specific times in the life cycle of the asynchronous event.
699+
* `userData` {Value} a value that will be passed to all callbacks.
703700

704701
Returns a constructed `AsyncListener` object.
705702

706-
To begin capturing asynchronous events pass the object to
707-
[`process.addAsyncListener()`][]. The same `AsyncListener` instance can
708-
only be added once to the active queue, and subsequent attempts to add the
709-
instance will be ignored.
703+
To begin capturing asynchronous events pass either the `callbacksObj` or
704+
and existing `AsyncListener` instance to [`process.addAsyncListener()`][].
705+
The same `AsyncListener` instance can only be added once to the active
706+
queue, and subsequent attempts to add the instance will be ignored.
710707

711-
To stop capturing pass the object to [`process.removeAsyncListener()`][].
712-
This does _not_ mean the `AsyncListener` previously added will stop
713-
triggering callbacks. Once attached to an asynchronous event it will
714-
persist with the lifetime of the asynchronous call stack.
708+
To stop capturing pass the `AsyncListener` instance to
709+
[`process.removeAsyncListener()`][]. This does _not_ mean the
710+
`AsyncListener` previously added will stop triggering callbacks. Once
711+
attached to an asynchronous event it will persist with the lifetime of the
712+
asynchronous call stack.
715713

716714
Explanation of function parameters:
717715

718-
`asyncListener(storageValue)`: A `Function` called when an asynchronous
716+
717+
`callbacksObj`: An `Object` which may contain three optional fields:
718+
719+
* `create(userData)`: A `Function` called when an asynchronous
719720
event is instantiated. If a `Value` is returned then it will be attached
720721
to the event and overwrite any value that had been passed to
721-
`process.createAsyncListener()`'s `storageValue` argument. If an initial
722-
`storageValue` was passed when created, then `asyncListener()` will
722+
`process.createAsyncListener()`'s `userData` argument. If an initial
723+
`userData` was passed when created, then `create()` will
723724
receive that as a function argument.
724725

725-
`callbacksObj`: An `Object` which may contain three optional fields:
726-
727-
* `before(context, storageValue)`: A `Function` that is called immediately
726+
* `before(context, userData)`: A `Function` that is called immediately
728727
before the asynchronous callback is about to run. It will be passed both
729-
the `context` (i.e. `this`) of the calling function and the `storageValue`
730-
either returned from `asyncListener` or passed during construction (if
728+
the `context` (i.e. `this`) of the calling function and the `userData`
729+
either returned from `create()` or passed during construction (if
731730
either occurred).
732731

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

737-
* `error(storageValue, error)`: A `Function` called if the event's
738-
callback threw. If `error` returns `true` then Node will assume the error
739-
has been properly handled and resume execution normally. When multiple
740-
`error()` callbacks have been registered, only **one** of those callbacks
741-
needs to return `true` for `AsyncListener` to accept that the error has
742-
been handled.
736+
* `error(userData, error)`: A `Function` called if the event's
737+
callback threw. If this registered callback returns `true` then Node will
738+
assume the error has been properly handled and resume execution normally.
739+
When multiple `error()` callbacks have been registered only **one** of
740+
those callbacks needs to return `true` for `AsyncListener` to accept that
741+
the error has been handled, but all `error()` callbacks will always be run.
743742

744-
`storageValue`: A `Value` (i.e. anything) that will be, by default,
743+
`userData`: A `Value` (i.e. anything) that will be, by default,
745744
attached to all new event instances. This will be overwritten if a `Value`
746-
is returned by `asyncListener()`.
745+
is returned by `create()`.
747746

748-
Here is an example of overwriting the `storageValue`:
747+
Here is an example of overwriting the `userData`:
749748

750-
process.createAsyncListener(function listener(value) {
751-
// value === true
752-
return false;
749+
process.createAsyncListener({
750+
create: function listener(value) {
751+
// value === true
752+
return false;
753753
}, {
754754
before: function before(context, value) {
755755
// value === false
756756
}
757757
}, true);
758758

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

764764

765-
## process.addAsyncListener(asyncListener[, callbacksObj[, storageValue]])
765+
## process.addAsyncListener(callbacksObj[, userData])
766766
## process.addAsyncListener(asyncListener)
767767

768768
Returns a constructed `AsyncListener` object and immediately adds it to
@@ -774,36 +774,32 @@ object.
774774

775775
Example usage for capturing errors:
776776

777+
var fs = require('fs');
778+
777779
var cntr = 0;
778-
var key = process.addAsyncListener(function() {
779-
return { uid: cntr++ };
780-
}, {
780+
var key = process.addAsyncListener({
781+
create: function onCreate() {
782+
return { uid: cntr++ };
783+
},
781784
before: function onBefore(context, storage) {
782-
// Need to remove the listener while logging or will end up
783-
// with an infinite call loop.
784-
process.removeAsyncListener(key);
785-
console.log('uid: %s is about to run', storage.uid);
786-
process.addAsyncListener(key);
785+
// Write directly to stdout or we'll enter a recursive loop
786+
fs.writeSync(1, 'uid: ' + storage.uid + ' is about to run\n');
787787
},
788788
after: function onAfter(context, storage) {
789-
process.removeAsyncListener(key);
790-
console.log('uid: %s is about to run', storage.uid);
791-
process.addAsyncListener(key);
789+
fs.writeSync(1, 'uid: ' + storage.uid + ' is about to run\n');
792790
},
793791
error: function onError(storage, err) {
794792
// Handle known errors
795-
if (err.message === 'really, it\'s ok') {
796-
process.removeAsyncListener(key);
797-
console.log('handled error just threw:');
798-
console.log(err.stack);
799-
process.addAsyncListener(key);
793+
if (err.message === 'everything is fine') {
794+
fs.writeSync(1, 'handled error just threw:\n');
795+
fs.writeSync(1, err.stack + '\n');
800796
return true;
801797
}
802798
}
803799
});
804800

805801
process.nextTick(function() {
806-
throw new Error('really, it\'s ok');
802+
throw new Error('everything is fine');
807803
});
808804

809805
// Output:
@@ -820,16 +816,19 @@ Example usage for capturing errors:
820816

821817
Removes the `AsyncListener` from the listening queue.
822818

823-
Removing the `AsyncListener` from the queue does _not_ mean asynchronous
824-
events called during its execution scope will stop firing callbacks. Once
825-
attached to an event it will persist for the entire asynchronous call
826-
stack. For example:
819+
Removing the `AsyncListener` from the active queue does _not_ mean the
820+
`asyncListener` callbacks will cease to fire on the events they've been
821+
registered. Subsequently, any asynchronous events fired during the
822+
execution of a callback will also have the same `asyncListener` callbacks
823+
attached for future execution. For example:
827824

828-
var key = process.createAsyncListener(function asyncListener() {
829-
// To log we must stop listening or we'll enter infinite recursion.
830-
process.removeAsyncListener(key);
831-
console.log('You summoned me?');
832-
process.addAsyncListener(key);
825+
var fs = require('fs');
826+
827+
var key = process.createAsyncListener({
828+
create: function asyncListener() {
829+
// Write directly to stdout or we'll enter a recursive loop
830+
fs.writeSync(1, 'You summoned me?\n');
831+
}
833832
});
834833

835834
// We want to begin capturing async events some time in the future.
@@ -861,11 +860,13 @@ To stop capturing from a specific asynchronous event stack
861860
`process.removeAsyncListener()` must be called from within the call
862861
stack itself. For example:
863862

864-
var key = process.createAsyncListener(function asyncListener() {
865-
// To log we must stop listening or we'll enter infinite recursion.
866-
process.removeAsyncListener(key);
867-
console.log('You summoned me?');
868-
process.addAsyncListener(key);
863+
var fs = require('fs');
864+
865+
var key = process.createAsyncListener({
866+
create: function asyncListener() {
867+
// Write directly to stdout or we'll enter a recursive loop
868+
fs.writeSync(1, 'You summoned me?\n');
869+
}
869870
});
870871

871872
// We want to begin capturing async events some time in the future.

lib/domain.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ exports._stack = stack;
4242
exports.active = null;
4343

4444

45-
function noop() { }
46-
47-
4845
var listenerObj = {
4946
error: function errorHandler(domain, er) {
5047
var caught = false;
@@ -104,7 +101,7 @@ inherits(Domain, EventEmitter);
104101
function Domain() {
105102
EventEmitter.call(this);
106103
this.members = [];
107-
this._listener = process.createAsyncListener(noop, listenerObj, this);
104+
this._listener = process.createAsyncListener(listenerObj, this);
108105
}
109106

110107
Domain.prototype.members = undefined;

src/node.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,21 @@
311311
inAsyncTick = true;
312312
for (i = 0; i < asyncQueue.length; i++) {
313313
queueItem = asyncQueue[i];
314+
if (!queueItem.callbacks.create) {
315+
queue[i] = queueItem;
316+
continue;
317+
}
314318
// Not passing "this" context because it hasn't actually been
315319
// instantiated yet, so accessing some of the object properties
316320
// can cause a segfault.
317321
// Passing the original value will allow users to manipulate the
318322
// original value object, while also allowing them to return a
319323
// new value for current async call tracking.
320-
value = queueItem.listener(queueItem.value);
324+
value = queueItem.callbacks.create(queueItem.value);
321325
if (typeof value !== 'undefined') {
322326
item = {
323327
callbacks: queueItem.callbacks,
324328
value: value,
325-
listener: queueItem.listener,
326329
uid: queueItem.uid
327330
};
328331
} else {
@@ -388,22 +391,19 @@
388391

389392
// Create new async listener object. Useful when instantiating a new
390393
// object and want the listener instance, but not add it to the stack.
391-
function createAsyncListener(listener, callbacks, value) {
394+
function createAsyncListener(callbacks, value) {
392395
return {
393396
callbacks: callbacks,
394397
value: value,
395-
listener: listener,
396398
uid: uid++
397399
};
398400
}
399401

400402
// Add a listener to the current queue.
401-
function addAsyncListener(listener, callbacks, value) {
403+
function addAsyncListener(callbacks, value) {
402404
// Accept new listeners or previous created listeners.
403-
if (typeof listener === 'function')
404-
callbacks = createAsyncListener(listener, callbacks, value);
405-
else
406-
callbacks = listener;
405+
if (typeof callbacks.uid !== 'number')
406+
callbacks = createAsyncListener(callbacks, value);
407407

408408
var inQueue = false;
409409
// The asyncQueue will be small. Probably always <= 3 items.

test/simple/test-asynclistener-error-add-after.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ var caught = 0;
3030
var expectCaught = 0;
3131
var exitCbRan = false;
3232

33-
function asyncL() { }
34-
3533
var callbacksObj = {
3634
error: function(value, er) {
3735
var idx = errorMsgs.indexOf(er.message);
@@ -48,7 +46,7 @@ var callbacksObj = {
4846
}
4947
};
5048

51-
var listener = process.createAsyncListener(asyncL, callbacksObj);
49+
var listener = process.createAsyncListener(callbacksObj);
5250

5351
process.on('exit', function(code) {
5452
// Just in case.

test/simple/test-asynclistener-error-multiple-handled.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,24 @@ function onAsync1() {
3333
return 1;
3434
}
3535

36+
function onError(stor) {
37+
results.push(stor);
38+
return true;
39+
}
40+
3641
var results = [];
37-
var asyncNoHandleError = {
38-
error: function(stor) {
39-
results.push(stor);
40-
return true;
41-
}
42+
var asyncNoHandleError0 = {
43+
create: onAsync0,
44+
error: onError
45+
};
46+
var asyncNoHandleError1 = {
47+
create: onAsync1,
48+
error: onError
4249
};
4350

4451
var listeners = [
45-
process.addAsyncListener(onAsync0, asyncNoHandleError),
46-
process.addAsyncListener(onAsync1, asyncNoHandleError)
52+
process.addAsyncListener(asyncNoHandleError0),
53+
process.addAsyncListener(asyncNoHandleError1)
4754
];
4855

4956
process.nextTick(function() {

test/simple/test-asynclistener-error-multiple-mix.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
var common = require('../common');
2323
var assert = require('assert');
2424

25-
function onAsync() {}
26-
2725
var results = [];
2826
var asyncNoHandleError = {
2927
error: function(stor) {
@@ -39,8 +37,8 @@ var asyncHandleError = {
3937
};
4038

4139
var listeners = [
42-
process.addAsyncListener(onAsync, asyncHandleError),
43-
process.addAsyncListener(onAsync, asyncNoHandleError)
40+
process.addAsyncListener(asyncHandleError),
41+
process.addAsyncListener(asyncNoHandleError)
4442
];
4543

4644
// Even if an error handler returns true, both should fire.

test/simple/test-asynclistener-error-multiple-unhandled.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,23 @@ function onAsync1() {
3030
return 1;
3131
}
3232

33+
function onError(stor) {
34+
results.push(stor);
35+
}
36+
3337
var results = [];
34-
var asyncNoHandleError = {
35-
error: function(stor) {
36-
results.push(stor);
37-
}
38+
var asyncNoHandleError0 = {
39+
create: onAsync0,
40+
error: onError
41+
};
42+
var asyncNoHandleError1 = {
43+
create: onAsync1,
44+
error: onError
3845
};
3946

4047
var listeners = [
41-
process.addAsyncListener(onAsync0, asyncNoHandleError),
42-
process.addAsyncListener(onAsync1, asyncNoHandleError)
48+
process.addAsyncListener(asyncNoHandleError0),
49+
process.addAsyncListener(asyncNoHandleError1)
4350
];
4451

4552
var uncaughtFired = false;

0 commit comments

Comments
 (0)