From e37d5026d42a2895bfb27f247a38da2de2365226 Mon Sep 17 00:00:00 2001 From: kschat Date: Thu, 7 Jul 2016 14:02:11 -0400 Subject: [PATCH] Add option to disable injecting context into error --- README.md | 6 +++++- context.js | 25 ++++++++++++++++--------- test/error-handling.tap.js | 25 +++++++++++++++++++++++++ test/namespaces.tap.js | 7 ++++++- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1bd5768..278101a 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ function requestHandler() { } ``` -## cls.createNamespace(name) +## cls.createNamespace(name, [options]) * return: {Namespace} @@ -120,6 +120,10 @@ Each application wanting to use continuation-local values should create its own namespace. Reading from (or, more significantly, writing to) namespaces that don't belong to you is a faux pas. +By default the current context will be added to any thrown error running in a +namespace. If you wish to disable this behavior, set `options.injectErrorContext` +to `false` (defaults to `true`). + ## cls.getNamespace(name) * return: {Namespace} diff --git a/context.js b/context.js index 93972c5..f33a18e 100644 --- a/context.js +++ b/context.js @@ -14,12 +14,13 @@ var ERROR_SYMBOL = 'error@context'; // load polyfill if native support is unavailable if (!process.addAsyncListener) require('async-listener'); -function Namespace(name) { - this.name = name; +function Namespace(name, injectErrorContext) { + this.name = name; // changed in 2.7: no default context - this.active = null; - this._set = []; - this.id = null; + this.active = null; + this._set = []; + this.id = null; + this.injectErrorContext = injectErrorContext; } Namespace.prototype.set = function (key, value) { @@ -49,7 +50,7 @@ Namespace.prototype.run = function (fn) { return context; } catch (exception) { - if (exception) { + if (this.injectErrorContext && exception) { exception[ERROR_SYMBOL] = context; } throw exception; @@ -76,7 +77,7 @@ Namespace.prototype.bind = function (fn, context) { return fn.apply(this, arguments); } catch (exception) { - if (exception) { + if (this.injectErrorContext && exception) { exception[ERROR_SYMBOL] = context; } throw exception; @@ -160,10 +161,16 @@ function get(name) { return process.namespaces[name]; } -function create(name) { +function create(name, options) { assert.ok(name, "namespace must be given a name!"); - var namespace = new Namespace(name); + options = options || {}; + // `== null` tests for `null` and `undefined` + if (options.injectErrorContext == null) { + options.injectErrorContext = true; + } + + var namespace = new Namespace(name, options.injectErrorContext); namespace.id = process.addAsyncListener({ create : function () { return namespace.active; }, before : function (context, storage) { if (storage) namespace.enter(storage); }, diff --git a/test/error-handling.tap.js b/test/error-handling.tap.js index f792dbb..052784b 100644 --- a/test/error-handling.tap.js +++ b/test/error-handling.tap.js @@ -82,6 +82,31 @@ test("synchronous throw checks if error exists", function (t) { cls.destroyNamespace('cls@synchronous-null-error'); }); +test("synchronous throw doesn't attach context", function (t) { + t.plan(2); + + var namespace = cls.createNamespace('cls@synchronous-not-attached', { + injectErrorContext: false + }); + + namespace.run(function () { + namespace.set('value', 'transaction clear'); + try { + namespace.run(function () { + namespace.set('value', 'transaction set'); + throw new Error('cls@synchronous explosion'); + }); + } + catch (e) { + t.notOk(namespace.fromException(e), "context was not attached to error"); + } + + t.equal(namespace.get('value'), 'transaction clear', "everything was reset"); + }); + + cls.destroyNamespace('cls@synchronous-not-attached'); +}); + test("throw in process.nextTick attaches the context", function (t) { t.plan(3); diff --git a/test/namespaces.tap.js b/test/namespaces.tap.js index 2ffa577..d2d775d 100644 --- a/test/namespaces.tap.js +++ b/test/namespaces.tap.js @@ -6,7 +6,7 @@ var test = tap.test; var context = require('../context.js'); test("namespace management", function (t) { - t.plan(8); + t.plan(10); t.throws(function () { context.createNamespace(); }, "name is required"); @@ -26,4 +26,9 @@ test("namespace management", function (t) { "destroying works"); t.notOk(process.namespaces.another, "namespace has been removed"); + + t.ok(namespace.injectErrorContext, "defaults to injecting error context"); + + namespace = context.createNamespace('explictOption', { injectErrorContext: false }); + t.notOk(namespace.injectErrorContext, "setting injectErrorContext works"); });