JSPM

  • Created
  • Published
  • Downloads 1907055
  • Score
    100M100P100Q201960F
  • License BSD

userland implementation of https://github.com/joyent/node/issues/5243

Package Exports

  • continuation-local-storage

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (continuation-local-storage) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Continuation-Local Storage

Stability: 1 - Experimental

Continuation-local storage provides a mechanism similar to thread-local storage in threaded programming, with closures wrapped around portions of a continuation chain taking the place of mutable cells bound to thread data structures. Contexts are created on namespaces and can be be nested.

Every namespace is created with a default context. The currently active context on a namespace is available via namespace.active.

var cls = require('contination-local-storage');

var writer = cls.createNamespace('writer');
writer.set('value', 0);

function requestHandler() {
  writer.run(function (outer) {
    // writer.get('value') returns 0
    // outer.value is 0
    writer.set('value', 1);
    // writer.get('value') returns 1
    // outer.value is 1
    process.nextTick(function () {
      // writer.get('value') returns 1
      // outer.value is 1
      writer.run(function (inner) {
        // writer.get('value') returns 1
        // outer.value is 1
        // inner.value is 1
        writer.set('value', 2);
        // writer.get('value') returns 2
        // outer.value is 1
        // inner.value is 2
      });
    });
  });

  setTimeout(function () {
    // runs with the default context, because nested contexts have ended
    console.log(writer.get('value')); // prints 0
  }, 1000);
}

cls.createNamespace(name)

  • return: {Namespace}

Each application that wants to use continuation-local attributes should create its own namespace. Reading from (or, more significantly, writing to) namespaces that don't belong to you should be considered a faux pas.

cls.getNamespace(name)

  • return: {Namespace}

Look up an existing namespace.

process.namespaces

  • return: dictionary of {Namespace} objects

Set of available namespaces.

Class: Namespace

Application-specific namespaces provide access to continuation-local attributes, and may have specialized behavior on a per-namespace basis (custom nesting behavior). Once the execution of a handler chain begins, creating new contexts and changing local values should be considered mutating the value of a given attribute on that particular continuation chain.

namespace.active

Returns the currently active context on a namespace.

namespace.set(key, value)

Set a value on the current continuation context. Shorthand for namespace.active.set(key, value).

Returns the value.

namespace.get(key)

Look up a value on the current continuation context. Recursively searches from the innermost to outermost nested continuation context for a value associated with a given key.

namespace.run(continuation)

Create a new scope to which attributes can be bound or mutated. Run the continuation in this new scope (passing in the new context into the continuation). It also returns the context that just exited in case you want to read data from it.

namespace.bind(callback, [context])

Bind a function to the specified continuation context. Works analagously to Function.bind() or domain.bind(). If context is omitted, it will default to the currently active context in the namespace.

context

A context is just a plain object that inherits from it's parent context.

Rationale

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 joyent/node#3733.

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.

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

var cls = require('continuation-local-storage');

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

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

Trace.prototype.runHandler = function (callback) {
  var trace = tracer.run(callback);
  this.harvester.emit('finished', trace.transaction);
};

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