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

Bug fix - listener leaks in case of nested wrapped methods and an error in the method #38

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
13 changes: 12 additions & 1 deletion glue.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ var asyncCatcher;
*/
var asyncWrap;

/**
* Counter for the number of nested wrappings in the same tick, used when poping from listenerStack to pop the right
* number of frames
*/
var nestedAsyncWraps = 0;

/**
* Simple helper function that's probably faster than using Array
* filter methods and can be inlined.
Expand Down Expand Up @@ -128,7 +134,10 @@ if (process._fatalException) {
* synchronous throws when the listener is active, there may have been
* none pushed yet.
*/
if (listenerStack.length > 0) listeners = listenerStack.pop();
while (nestedAsyncWraps > 0) {
if (listenerStack.length > 0) listeners = listenerStack.pop();
nestedAsyncWraps--;
}
errorValues = undefined;

return handled && !inAsyncTick;
Expand Down Expand Up @@ -167,6 +176,7 @@ if (process._fatalException) {
* current listeners on a stack.
*/
listenerStack.push(listeners);
nestedAsyncWraps++;

/* Activate both the listeners that were active when the closure was
* created and the listeners that were previously active.
Expand Down Expand Up @@ -200,6 +210,7 @@ if (process._fatalException) {

// back to the previous listener list on the stack
listeners = listenerStack.pop();
nestedAsyncWraps--;
errorValues = undefined;

return returned;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
var domain = require('domain');
var test = require('tap').test;
if (!process.addAsyncListener) require('../index.js');

/**
* if we import domains after async-listener, the domain import causes process.nextTick to call async listener twice.
* The reason is because domain replaces the underlying _currentTickHandler. If domain is loaded after async listener,
* the _currentTickHandler method is replaced with a wrapped method (wrapped by async listener).
*
* The net result is that
* if we load async-listener, then domain, we will have both process.nextTick and _currentTickHandler wrapped.
*
* if we load domain, then async-listener, we will have only process.nextTick wrapped.
*
* This double wrapping causes another issue - if we have an exception in the async method, asycn-listener does not expect a method
* to be wrapped twice and will only pop the listenerStack once, making calls after that that should not respond to the listener do be effected.
*
* **/
test("asyncListeners with domains", function (t) {
t.plan(2);

t.test("illustration of the problem - create is called twice", function(t) {
t.plan(1);
var callsToCreate = 0;
var listener = process.createAsyncListener(
{
create : function () { callsToCreate++; },
before : function () {},
after : function () {},
error : function () {}
}
);

process.addAsyncListener(listener);
process.nextTick(function Func() {
t.equal(callsToCreate, 1, "number of calls to create");
});
process.removeAsyncListener(listener);


});

t.test("validate that async listener create is not called for a tick executed after a listened on tick with an error", function(t) {
t.plan(2);
var asyncListenedMethodDone = false;
var callsToCreate = 0;
var listener = process.createAsyncListener(
{
create : function () { callsToCreate++; },
before : function () {},
after : function () {},
error : function () {return true;}
}
);

process.addAsyncListener(listener);
process.nextTick(function Func() {
asyncListenedMethodDone = true;
throw new Error("Bla");
});
process.removeAsyncListener(listener);

// wait until the error was raised and then perform async operation
var timer = setInterval(function() {
if (asyncListenedMethodDone) {
t.equal(callsToCreate, 1, "number of calls to create - before the async method");
clearInterval(timer);
process.nextTick(function() {
t.equal(callsToCreate, 1, "number of calls to create - in the async method (who should not be listened on by async the registered listener");
})
}
}, 1);
})
});