Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

context: core module to manage generic contexts for async call chains #5243

Closed
@othiym23

Description

@othiym23

The domains mechanism is a useful tool for adding context to errors raised in asynchronous call chains (or, if you like living dangerously / tempting the wrath of @isaacs, to recover from errors without restarting your services). It also almost serves the purposes of developers who want to annotate async call chains with metadata or extra state (examples: logging, tracing / profiling, generic instrumentation), but due to the needs of error-handling, it doesn't quite generalize enough to be truly useful in this regard. There are modules that allow developers to do similar things when they have full control over their stacks (CrabDude/trycatch and Gozala/reducers, among many others), but none of these modules are a good fit for developers writing tooling meant to be dropped transparently into user code.

See also #3733. /cc @isaacs

Here is a sketch at what the user-visible API might look like. My original attempt at this used a slightly modified version of the domains API with some special-purpose logic for dealing with nested contexts, but allowing multiple distinct namespaces is actually simpler and trades memory for execution time. It also makes it possible to special-case behavior for specific namespaces (i.e. my hope would be that domains would just become a specialized namespace, and _tickDomainCallback and _nextDomainTick would be all that would be required to deal with namespaces), although that isn't included here.

The API (context.js):

var util = require('util')
  , EventEmitter = require('events').EventEmitter
  ;

/* Not including the rest of the namespace management code in node.js / node.cc,
 * which is largely analogous to the way domains are implemented in 0.8 / 0.10.
 */
var namespaces = process.namespaces = Object.create(null);


function Context(namespace) {
  EventEmitter.call(this);
  this.namespace = namespace;
  this.bag = Object.create(null);
}
util.inherits(Context, EventEmitter);

Context.prototype.enter = function () { this.namespace.active = this; };
Context.prototype.exit = function () { delete this.namespace.active; };
// TODO: Context.prototype.bind = function () {};
// TODO: Context.prototype.add = function () {};
Context.prototype.end = function () { this.emit('end'); };
Context.prototype.set = function (key, value) { this.bag[key] = value; };
Context.prototype.get = function (key) { return this.bag[key]; };
Context.prototype.run = function (callback) {
  this.enter();
  callback.call(this);
  this.exit();
  this.end();
};


function Namespace(name) {
  this.name = name;
  namespaces[name] = this;
}

Namespace.prototype.createContext = function () {
  var context = new Context(this);
};


var context = module.exports = {
  // multiple namespaces for multiple purposes, e.g. domains
  createNamespace : function (name) { return new Namespace(name); },
  getNamespace : function (name) { return namespaces[name]; }
};

Here's an example of how the API might be used:

var context = require('context');

// multiple contexts in use
var tracer = context.createNamespace('tracer');

function Trace(harvester) {
  this.harvester = harvester;
}

Trace.prototype.runHandler = function (callback) {
  var trace = tracer.createContext();

  trace.on('end', function () {
    var transaction = trace.get('transaction');
    this.harvester.emit('finished', transaction);
  };

  trace.run(callback);
};

Trace.prototype.annotateState = function (name, value) {
  var active = tracer.active;
  active.set(name, value);
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions