Skip to content

Instantly share code, notes, and snippets.

@groundwater groundwater/About.md Secret
Created Mar 25, 2014

Embed
What would you like to do?

I have ammended the context.js file in continuation-local-storage to print relevant info with process._rawDebug.

Each time you create a namespace with cls.createNamespace it adds a new async listener.

When an AL is attached between a BEFORE/AFTER event, the newly attached listeners AFTER event seems to be firing. It's BEFORE listener never fired because the listener did not exist at that time.

CLS assumes that before will always occur before an after event.

'use strict';
var assert = require('assert');
var wrapEmitter = require('emitter-listener');
var shimmer = require('shimmer');
var tracing = require('tracing');
/*
*
* CONSTANTS
*
*/
var CONTEXTS_SYMBOL = 'cls@contexts';
var ERROR_SYMBOL = 'error@context';
var namespaces;
function Namespace(name) {
this.name = name;
// every namespace has a default / "global" context
this.active = Object.create(null);
this._set = [];
this.id = null;
}
Namespace.prototype.set = function (key, value) {
this.active[key] = value;
return value;
};
Namespace.prototype.get = function (key) {
return this.active[key];
};
Namespace.prototype.createContext = function () {
return Object.create(this.active);
};
Namespace.prototype.run = function (fn) {
var context = this.createContext();
this.enter(context);
try {
fn(context);
return context;
}
catch (exception) {
exception[ERROR_SYMBOL] = context;
throw exception;
}
finally {
this.exit(context);
}
};
Namespace.prototype.bind = function (fn, context) {
if (!context) context = this.active;
var self = this;
return function () {
self.enter(context);
try {
return fn.apply(this, arguments);
}
catch (exception) {
exception[ERROR_SYMBOL] = context;
throw exception;
}
finally {
self.exit(context);
}
};
};
Namespace.prototype.enter = function (context) {
assert.ok(context, "context must be provided for entering");
this._set.push(this.active);
this.active = context;
};
Namespace.prototype.exit = function (context) {
assert.ok(context, "context must be provided for exiting");
// Fast path for most exits that are at the top of the stack
if (this.active === context) {
assert.ok(this._set.length, "can't remove top context");
this.active = this._set.pop();
return;
}
// Fast search in the stack using lastIndexOf
var index = this._set.lastIndexOf(context);
assert.ok(index >= 0, "context not currently entered; can't exit");
assert.ok(index, "can't remove top context");
this._set.splice(index, 1);
};
Namespace.prototype.bindEmitter = function (emitter) {
assert.ok(emitter.on && emitter.addListener && emitter.emit, "can only bind real EEs");
var namespace = this;
var thisSymbol = 'context@' + this.name;
// Capture the context active at the time the emitter is bound.
function attach(listener) {
if (!listener) return;
if (!listener[CONTEXTS_SYMBOL]) listener[CONTEXTS_SYMBOL] = Object.create(null);
listener[CONTEXTS_SYMBOL][thisSymbol] = {
namespace : namespace,
context : namespace.active
};
}
// At emit time, bind the listener within the correct context.
function bind(unwrapped) {
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) return unwrapped;
var wrapped = unwrapped;
var contexts = unwrapped[CONTEXTS_SYMBOL];
Object.keys(contexts).forEach(function (name) {
var thunk = contexts[name];
wrapped = thunk.namespace.bind(wrapped, thunk.context);
});
return wrapped;
}
wrapEmitter(emitter, attach, bind);
};
/**
* If an error comes out of a namespace, it will have a context attached to it.
* This function knows how to find it.
*
* @param {Error} exception Possibly annotated error.
*/
Namespace.prototype.fromException = function (exception) {
return exception[ERROR_SYMBOL];
};
function get(name) {
return namespaces[name];
}
var j = 0;
function create(name) {
j++;
assert.ok(name, "namespace must be given a name!");
var namespace = new Namespace(name);
var i = 0;
namespace.id = tracing.addAsyncListener(
{
create : function ME() {
var x = {}
Error.captureStackTrace(x, ME);
process._rawDebug(' + CREATE', j, ++i);
process._rawDebug(x.stack.split('\n').slice(1,5).join('\n'));
return {
d: namespace.active,
i: i
};
},
before : function (context, domain) {
process._rawDebug('\n--> BEFORE', j, domain.i);
namespace.enter(domain.d);
},
after : function (context, domain) {
process._rawDebug('<-- AFTER ', j, domain.i);
namespace.exit(domain.d);
},
error : function (ctx, domain, err) {
if (domain) namespace.exit(domain);
}
}
);
namespaces[name] = namespace;
return namespace;
}
function destroy(name) {
var namespace = get(name);
assert.ok(namespace, "can't delete nonexistent namespace!");
assert.ok(namespace.id, "don't assign to process.namespaces directly!");
tracing.removeAsyncListener(namespace.id);
namespaces[name] = null;
}
function reset() {
// must unregister async listeners
if (namespaces) {
Object.keys(namespaces).forEach(function (name) {
destroy(name);
});
}
namespaces = process.namespaces = Object.create(null);
}
reset(); // call immediately to set up
module.exports = {
getNamespace : get,
createNamespace : create,
destroyNamespace : destroy,
reset : reset
};
+ CREATE 1 1
at runAsyncQueue (tracing.js:236:23)
at exports.setImmediate (timers.js:437:5)
at global.setImmediate (node.js:198:29)
at Object.<anonymous> (/Users/jacob/Projects/continuation-local-storage/test.js:4:1)
--> BEFORE 1 1
<-- AFTER 2 1
assert.js:98
throw new assert.AssertionError({
^
AssertionError: context not currently entered; can't exit
at Namespace.exit (/Users/jacob/Projects/continuation-local-storage/context.js:93:10)
at AsyncListener.namespace.id.tracing.addAsyncListener.error (/Users/jacob/Projects/continuation-local-storage/context.js:174:31)
at errorHandler (tracing.js:370:21)
at process._fatalException (node.js:231:20)
var cls = require('continuation-local-storage');
var ns = cls.createNamespace("ns1");
setImmediate(function () {
cls.createNamespace('ns2');
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.