Skip to content

Instantly share code, notes, and snippets.

@groundwater groundwater/context.js Secret
Created Mar 21, 2014

Embed
What would you like to do?
'use strict';
var assert = require('assert');
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];
}
function create(name) {
assert.ok(name, "namespace must be given a name!");
var namespace = new Namespace(name);
namespace.id = tracing.addAsyncListener(
{
create : function () {
return namespace.active;
},
before : function (context, domain) {
namespace.enter(domain);
},
after : function (context, domain) {
namespace.exit(domain);
},
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
};
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.