I know the rule "if you think environment is broken instead of your code, think again". But this problem is straining my resolve to follow it.
So, I have this generic error handler, that will try to callback with error, emit the error or throw the error, in order of preference.
exports.handleError = function(fallbackOrThrow, errType, err, callback, nextTick) {
if (!(err instanceof Error || libToolsTypes.isString(err))) {
nextTick = callback;
callback = err;
err = errType;
errType = null;
}
if (nextTick === true && callback) {
process.nextTick(exports.handleError.bind(this, fallbackOrThrow, errType, err, callback, false));
return true;
}
if (errType) {
if (errType instanceof Error) {
if (!(err instanceof errType)) {
err = new errType(err);
}
}
else if (libToolsTypes.isFunction(errType)) {
err = errType(err);
}
}
if (callback && callback.call) {
callback.call(this, err);
return true;
}
if (fallbackOrThrow && fallbackOrThrow.emit) {
fallbackOrThrow.emit("error", err);
return true;
}
if (libToolsTypes.isFunction(fallbackOrThrow)) {
return fallbackOrThrow(err);
}
if (fallbackOrThrow === true) {
throw err;
}
return false;
};
Sorry for the crud, I intentionally let it in just in case something here is causing issues. The important part is fallbackOrThrow
argument, which is usually EventEmitter
-derived object or "true" which means "throw the error if you can't handle it otherwise".
So here's the issue: Occasionally, very occasionally, the code reaches throw err;
path, even though I'm sure I've supplied a reference to an EventEmitter object. So far, this happens when called from a very specific part of the code, so the issue could be related to that somehow. This is how the calling code looks (once again, intentionally unfiltered):
function needToHandleLater(ob) {
if (_queue.length && _queue.length >= options.queueLength) {
noPlaceInQueueFor(_queue.pop());
}
if (_queue.length >= options.asyncQueueLength) {
noPlaceInQueueFor(ob);
} else {
_queue.push(ob);
}
function noPlaceInQueueFor(ob) {
tools.handleError(that, null, new NoPlaceInQueueError(), ob.callback);
}
}
that
is a (long term) reference to the current event emitter object.
So, you might think I made a mistake somewhere along the way and these arguments aren't what I think they are. However, I just did this:
// Log the state before we call handleError
console.log(that /*, a bunch of other values */);
tools.handleError(that, null, new NoPlaceInQueueError(), ob.callback);
// Immediately inside handleError, log just before we throw
if (fallbackOrThrow === true) {
console.log(fallbackOrThrow /*, a bunch of other values */);
throw err;
}
The output is (truncated "a bunch of other values" part):
>> { id: 7,
_events: {
// the rest of my event emitter code, expected
>> true // WTF!!? How did this get here?
So the EventEmitter
derived object somehow turned into true
one function call away. And I don't see how that is possible without some serious memory corruption. Or I'm just missing something glaringly obvious.
Which is really my hope with this question. Did someone experience anything like this? Is there a bug report or javascript gotcha I'm unaware of? Just hoping for a quick tip or link before I start adding inspector to the production server...
Node.js version: v0.10.28
EDIT 1
Stack trace. There's nothing special in it. It enters the expected functions and throws the error.
/home/project/node/Common/Tools/toolsFunctions.js:216
throw err;
^
NoPlaceInQueueError: There is no place in queue
at noPlaceInQueueFor (/home/project/node/MyObject/objectWithQueue.js:196:37)
at needToHandleLater (/home/project/node/MyObject/objectWithQueue.js:186:4)
(stack trace anonymized)