othiym23 / node-continuation-local-storage Goto Github PK
View Code? Open in Web Editor NEWimplementation of https://github.com/joyent/node/issues/5243
License: BSD 2-Clause "Simplified" License
implementation of https://github.com/joyent/node/issues/5243
License: BSD 2-Clause "Simplified" License
For some reason the CLS context get's lost on the setTimeout call. I'm resolve a promise on a setTimeout. If I resolve the immediately promise (w/out the setTimeout I still have the context) the values are defined, if I include the timeout the values are undefined.
Here is my example application I'm trying.
//index.js
'use strict';
var Bluebird = require('bluebird');
var testAsync = require('./test_async');
// namespace has to be created first before it can be used;
var tracer = require('./tracer');
setInterval(function () {
tracer.run(function () {
return testAsync()
.then(function () {
return new Bluebird(function (resolve) {
setTimeout(resolve, Math.random() * 500);
});
})
.then(function () {
console.log(new Date().getTime() + ' - tid test end', tracer.context.get('tid'));
console.log(new Date().getTime() + ' - test value test end', tracer.context.get('testVal'));
console.log('---------------------------------------------------------');
});
});
}, 2000);
// tracer.js
'use strict';
var cls = require('continuation-local-storage'),
uuid = require('node-uuid');
module.exports = {
get context() {
return cls.getNamespace('transaction') || cls.createNamespace('transaction');
},
run : function (_fn) {
this.context.run(function () {
this.context.set('tid', uuid.v4());
return _fn(arguments);
}.bind(this));
}
};
// test_async.js
'use strict';
var Bluebird = require('bluebird');
var tracer = require('./tracer');
module.exports = function () {
return Bluebird
.delay(1000)
.then(function () {
tracer.context.set('testVal', 1234);
return 3;
});
};
Here is the output I get
---------------------------------------------------------
1449865533912 - tid test end undefined
1449865533912 - test value test end undefined
---------------------------------------------------------
1449865536373 - tid test end undefined
1449865536373 - test value test end undefined
---------------------------------------------------------
1449865538012 - tid test end undefined
1449865538012 - test value test end undefined
---------------------------------------------------------
1449865540362 - tid test end undefined
...
In index.js when I change setTimeout(resolve, Math.random() * 500);
to just resolve();
I get
---------------------------------------------------------
1449865577071 - tid test end be1d48f2-6156-4912-8556-a5386d737f50
1449865577071 - test value test end 1234
---------------------------------------------------------
1449865579074 - tid test end 8df4c49a-d73a-4f19-ae53-c361cfadb39f
1449865579074 - test value test end 1234
---------------------------------------------------------
1449865581074 - tid test end 3f6916c3-c898-44f4-8149-a299e443dbee
1449865581074 - test value test end 1234
---------------------------------------------------------
1449865583080 - tid test end e0dd4973-468b-484f-aaf4-1e8364ecf9f5
1449865583080 - test value test end 1234
---------------------------------------------------------
...
When request does an HTTP GET to a machine where no server is listening on that port, it invokes the callback with an error object which is expected behavior. The context however in CLS is cleared and no values can be retrieved via get
var request = require('request');
var cls = require('continuation-local-storage');
var ns = cls.createNamespace('test');
ns.run(function() {
ns.set('foo', 'bar');
//just showing cls works fine and prints `bar` __after__ the the callback on request is invoked
setTimeout(function() {
console.log("This is fine " + ns.get('foo'));
}, 0);
//hit some random url where no server is listening
request('http://localhost:2382', function(err, resp, body) {
//this will print connect ECONNREFUSED
console.log(err);
//this will print undefined
console.log(ns.get('bar'));
});
});
As per this gist:
https://gist.github.com/dazld/6493549
output is as follows:
in dispatch: 1337038180313
handler: 1337038180313
routeMatched listener: undefined
seems like this should work, but can't see what might be causing the problem - any ideas..?
Using node 0.10.13 and CLS 2.1.1
@othiym23 can I ask your opinion on something? (this isn't an issue about CLS per se)
I've been having some problems using CLS, bluebird and co together, with CLS context sometimes getting lost. By way of trying to narrow down the problem, I've written a set of tests which run against various different bluebird CLS shims to see how they all behave in different circumstances.
Please see: https://www.github.com/overlookmotel/cls-bluebird-test
There's one weird case...
(NB cls-bluebird2 is the beginnings of an attempt at a new cls-bluebird shim)
var Promise = require('bluebird');
var cls = require('continuation-local-storage');
var clsBluebird = require('cls-bluebird2');
var assert = require('assert');
var ns = cls.createNamespace('test');
clsBluebird(ns, Promise);
// CASE 1
var promise;
ns.run(function() {
ns.set('test', 123);
promise = Promise.resolve().then(function() {
assert(ns.get('test') == 123);
});
});
promise.then(function() {
assert(ns.get('test') == 123);
});
Both assert statements pass in this case. I think that's the correct behaviour - the .then()
outside of ns.run()
should inherit CLS context from the previous promise in the chain.
But how about this:
// CASE 2
var promise;
ns.run(function() {
ns.set('test', 123);
promise = Promise.resolve();
});
promise.then(function() {
assert(ns.get('test') == 123);
});
In this 2nd case, the assert statement fails.
Should it? My first thought was that it should execute in the context of the promise chain, in the same way as case 1. On the other hand, it's chaining onto a promise which is already resolved. So in what sense is it "following on from" that promise in the way that a node callback "follows on" immediately from the function that calls it?
What's your opinion?
More generally speaking, there are quite a lot of subtleties in reasoning the desired behaviour of CLS context passing with promises. async-listener's shim of native promises takes the view that it's all about following the chain of execution down the promise chain (and so both the above cases pass), whereas cls-bluebird executes code in a .then()
in the context in which .then()
was called (so both above cases fail).
Has anyone written down any kind of spec, or made a set of tests to define desired behaviour?
I'm wondering whether it is OK when running it under Node 0.8.*.
Some of my apps throw exceptions.
Thanks!
BR//Ben
CLS seems to lose state when timeout or other connection error happens.
Only if I bindEmitter request object it would keep it.
This leads to modules that do http requests breaking CLS context when error occurs.
Can CLS do this automatically?
Here's test case:
var http = require('http')
, createNamespace = require('../context').createNamespace
;
var namespace = createNamespace('http');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
console.log('before: ', namespace.get('test'));
// var req = http.get('http://127.0.0.1:8080', function (res) { // REFUSED
var req = http.get('http://128.0.0.1:8080', function (res) { // TIMEOUT
res.on('data', function (chunk) {
});
res.on('end', function () {
console.log('response: ', namespace.get('test'));
});
});
// namespace.bindEmitter(req);
req.setTimeout(500, function() {
console.log('timeout: ', namespace.get('test')); // >> undefined
req.abort();
});
req.on('error', function(e) {
console.log('abort: ', namespace.get('test'));// >> undefined
console.log('error: ', e);
});
// write data to request body
req.write('data\n');
req.end();
});
This is not an issue, sorry, but this module seems to good to be true so I have to ask what the catch is? If there isn't any catch why is it not heavily depended upon by other modules and why aren't there gazillions of blogposts about it?
I'm writing it as a note to myself and maybe other guys who plan/use CLS (continuation-local-storage).
I met several practical problems with it. But my another issue hangs about half-year+ without an answer (not even sure if it's actual any more, after AL updates), so I'm not asking anything from devs. Anyway I'm not using CLS for anything serious any more.
Just noting the things I met for a year+ of trying to make it work (with occasional success), so that people know the difficulties ahead, cause some of them are rather hidden.
The patching problem:
The private versions patching problem:
node_modules
tree of my app, to see if there's a private version of a CLS-unfriendly module, and patch that. Every module/submodule/subsubmodule/... must support CLS or be patched for it.The dangerous bugs problem:
I admire the idea behind the module/async-listener. Async chains in Node.JS are like green threads. A context for an async chain is so cool, it's a must. It's like thread local variables, widely used by so many systems.
Maybe there's something that can be done in the node core to make it work everywhere, make every module CLS'able without critical perf loss?
It would be great to get more attention to the module and its functionality from the minds of the community.
How can i get cls to work with sequelize ?
I created a nameSpace for every request but its data is lost when sequelize is used.
Do i have to develop a shimmer or is there some nice workaround?
Using CLS with Mongoose is broken inside query callback function. The context is lost.
sample:
console.log("### outside mongoose query", ns.get("foo"));
SomeMongooseModel.find(query, function(err, obj){
console.log("### inside mongoose query", ns.get("foo"));
});
problem:
The problem is that the outside query code can get the variable from CLS namespace, while the inside query callback function cannot get the variable. It apparently lost the context.
temp-solution:
I find that explicitly call namespace bind to wrap the callback function can be workaround:
console.log("### outside mongoose query", ns.get("foo"));
SomeMongooseModel.find(query, ns.bind(function(err, obj){
console.log("### inside mongoose query", ns.get("foo"));
}));
However, this temp-solution require explicit call to wrap the callback function which is ugly and not easy to maintain in future.
supplymentary information:
Node version: v4.2.4
CLS version: 3.1.6
Mongoose version: 4.4.3
Are there any cleaner solution for this issue?
This seems to be an implementation of this paper:
http://cs.brown.edu/~sk/Publications/Papers/Published/mk-int-safe-state-web/paper.pdf
Can it do the same things?
We are using continuation-local-storage in LoopBack to provide a "current context" object preserved across async calls. We would like to provide the same functionality when LoopBack is running in a browser via browserify.
Is there any alternative to node-continuation-local-storage that works in browsers?
For context see CrabDude/trycatch#32.
One problem with AL, is that it's implemented at the wrong end of the stack to properly solve issues like this (CLS), because shared / long-lived EEs (e.g., sockets in request's forever-agent) can cause the wrong domain / context to be set in the EE's handlers.
The way to solve this is to attach the context at time of listen to a handler, not to EE.
// context A
var ee = new EventEmitter()
// ... later
// context B
ee.on('connect', function() {
// context B (not A, like domain default)
})
The code below uses bindEmitter
to bind the response object to the current context.
So onfinish
must keep the context, right?
// bind
process.namespaces.app.bindEmitter(this.res);
// plan onfinish in the future
this.res.once('finish', onfinish);
var mark = Math.random();
console.log(mark, process.namespaces.app.get('requestId'));
// onfinish will trigger later, but in current context?
function onfinish(event) {
console.log(mark, process.namespaces.app.get('requestId'));
}
For same mark
, the requestId
must be same?
I'm asking, because it's not.
On Node v12.2:
var cls = require('continuation-local-storage');
var ns = cls.createNamespace('ns');
ns.run(function() {
ns.set('foo', 3);
var p = new Promise(function(resolve) { resolve(); });
p
.then(function() {console.log('c1 foo', ns.get('foo'))})
.then(function() {console.log('c2 foo', ns.get('foo'))});
});
This outputs:
c1 foo 3
c2 foo undefined
I expect:
c1 foo 3
c2 foo 3
This looks like it's actually an issue with https://github.com/othiym23/async-listener, but I'm filing the issue here because it's where the problem manifests for me.
Hi @othiym23,
Just curious if there's any pattern or usage that supports callbacks to parent contexts from nested scopes. Imagine a scenario where you're wrapping N async operations in their own context and want to return data to a parent scope. With this usage the callbacks occur from the child context when ideally we want to restore the parent context.
I've tried a few variations of keeping track of parent context then exit/enter but this breaks when exit is implicitly called in run.
Something like:
"use strict";
const cls = require("continuation-local-storage");
const async = require("async");
const ns = cls.createNamespace("test-cls-ns");
const beginTx = function(name, functor) {
ns.run((context) => {
console.log("begin tx: ", name);
ns.set("name",name);
// cache the context?
//ns.set("context", context);
functor();
});
}
const endTx = function() {
console.log("end tx", ns.get("name"))
// exit context, returning to parent scope
//ns.exit(ns.get("context"));
}
const slowFunc = (name, cb) => {
const delay = Math.floor(Math.random() * 400) + 500;
beginTx(name, () => {
setTimeout(() => {
endTx();
cb(`slowly done ${name}`);
}, delay);
});
};
const testMultipleTransactions = function() {
beginTx("parent", () => {
slowFunc("child", (data) => {
console.log("result: ", data);
// end parent tx?
endTx();
})
});
}
testMultipleTransactions();
Thanks!
Hey @othiym23,
Hope you'll have some time to check this small test-case: https://github.com/DeTeam/cls-cluster
We got a problem trying to run cluster mode in an express app together with cls. Setting up this config
within a request (in a middleware) is fine, however when we want to use something "global" it fails. Guess it has something to do with how cluster
works underneath. Please check it out, mb you'll have some comments ;)
Node versions I tried this with: 0.12, 4.1
Object.create(null) is used to create new contexts, is this intentional?
I have an issue with jasmine where it is iterating over an error object (it's trying to do a pretty print) that has the context attached, and it is attempting to run hasOwnProperty on the context, which is failing as the method doesn't exist. I can work around it, but I'm wondering if there is a reason we can't just let context use Object.prototype.
If I modify net-events.tap.js to have the server do socket.destroy() rather than a socket.end(), I get a 'close' event in the client that does not get the client.write cls.
Should the 'close' event preserve the cls state? Thank you.
Here is the modified net-events.tap.js
'use strict';
var net = require('net')
, tap = require('tap')
, test = tap.test
, createNamespace = require('../context').createNamespace
;
test("continuation-local state with net connection", function (t) {
t.plan(4);
var namespace = createNamespace('net');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
var server;
namespace.run(function () {
namespace.set('test', 0x1337);
server = net.createServer(function (socket) {
t.equal(namespace.get('test'), 0x1337, "state has been mutated");
socket.on("data", function () {
t.equal(namespace.get('test'), 0x1337, "state is still preserved");
socket.destroy(); // DESTROY RATHER THAN END
server.close();
// socket.end("GoodBye");
});
});
server.listen(function () {
var address = server.address();
namespace.run(function () {
namespace.set("test", "MONKEY");
var client = net.connect(address.port, function () {
t.equal(namespace.get("test"), "MONKEY",
"state preserved for client connection");
client.write("Hello");
client.on("data", function () {
t.equal(namespace.get("test"), "MONKEY", "state preserved for client data");
});
client.on("close", function () { // CLOSE EVENT NOT GETTING CLS STATE
t.equal(namespace.get("test"), "MONKEY", "state preserved for client close");
t.end();
});
});
});
});
});
});
});
It looks like this finally clause has no test coverage, or at least all tests pass with it removed.
https://github.com/othiym23/node-continuation-local-storage/blob/master/context.js#L161
I have this situation :
// module1/index.js
// Exports a function that assumes a namespace is available, and
// contains a variable.
module.exports = {
foo : function () {
var namespace = require("continuation-local-storage").getNamespace("foo");
console.log(namespace);
console.log(namespace.get("bar"));
}
}
// module2/index.js
// Uses the function from module 1.
var module1 = require("module1");
var namespace = require("continuation-local-storage").createNamespace("foo");
namespace.run(function () {
namespace.set("bar", 42);
module1.foo();
});
I'm expecting the function module1.foo to be called, and the namespace "foo" to be available (containing the function bar").
In practice, I the namespace is null inside foo.
Note that the "local" version works :
var localModule = {
foo : function () {
var namespace = require("continuation-local-storage").getNamespace("foo");
console.log(namespace);
console.log(namespace.get("bar"));
}
};
var namespace = require("continuation-local-storage").createNamespace("foo");
namespace.run(function () {
namespace.set("bar", 42);
// Will print the namespace, and 42
localModule.foo();
});
What am I missing ?
Thanks
Currently .bind()
will bind to a static context that is passed in, or to the currently active context. It doesn't create a new context on each invocation.
var requestNs = require('continuation-local-storage').createNamespace('request');
var middleware = requestNs.bind(function (req, res, next) {
requestNs.set('req', req);
requestNs.set('res', res);
next();
});
app.use(middleware);
The above version of bind
will bind every request to the same context and overwrite previous values of 'req' and 'res'.
I'd like a version that will create a new context for me and won't override existing values. This would help for cases like middleware. Currently if I want a fresh context, I have to call run() and pass in a fresh closure each time:
var requestNs = require('continuation-local-storage').createNamespace('request');
function middleware(req, res, next) {
requestNs.run(function() {
requestNs.set('req', req);
requestNs.set('res', res);
next();
});
}
app.use(middleware);
But with the proposed .bind() logic, I wouldn't.
Here's a case you can play with without dependencies:
var ns = require('continuation-local-storage').createNamespace('ns');
function logEm(b) {
console.log(ns.get('a'), b);
}
// This implementation of fn shows the requested behavior and will log
// "1 0", "2 1", ...
/*function fn(b) {
ns.run(function() {
ns.set('a', b + 1);
setTimeout(logEm.bind(null, b), 1000);
});
}*/
// The current behavior will log "10 0", "11 1", ...
var fn = ns.bind(function(b) {
ns.set('a', b + 1);
setTimeout(logEm.bind(null, b), 1000);
});
var inc = 0;
setInterval(function() { fn(inc++); }, 100);
Happy to submit a PR if you like this idea.
Randomly, the newrelic module crash my application with this error :
/srv/data/web/vhosts/default/node_modules/newrelic/node_modules/continuation-local-storage/context.js:66
throw exception;
^
Error: parser error, 44 of 48 bytes parsed
at IncomingForm.write (/srv/data/web/vhosts/default/node_modules/formidable/lib/incoming_form.js:143:17)
at IncomingMessage.<anonymous> (/srv/data/web/vhosts/default/node_modules/formidable/lib/incoming_form.js:110:12)
at IncomingMessage.<anonymous> (/srv/data/web/vhosts/default/node_modules/newrelic/node_modules/continuation-local-storage/context.js:62:17)
at IncomingMessage.EventEmitter.emit (events.js:95:17)
at IncomingMessage.emitted [as emit] (/srv/data/web/vhosts/default/node_modules/newrelic/node_modules/continuation-local-storage/node_modules/emitter-listener/listener.js:122:21)
at IncomingMessage.<anonymous> (_stream_readable.js:746:14)
at IncomingMessage.EventEmitter.emit (events.js:92:17)
at IncomingMessage.emitted [as emit] (/srv/data/web/vhosts/default/node_modules/newrelic/node_modules/continuation-local-storage/node_modules/emitter-listener/listener.js:122:21)
at emitReadable_ (_stream_readable.js:408:10)
at emitReadable (_stream_readable.js:404:5)
I use this modules :
"express": "~3.4.7",
"ejs": "~0.8.5",
"mongoose": "~3.8.3",
"validator": "~2.0.0",
"mandrill-api": "~1.0.37",
"formidable": "~1.0.14",
"newrelic": "~1.2.0",
"csv": "~0.3.6"
With node v0.10.24
Does anyone known where the problem come from ?
Async-listener just merged this PR: othiym23/async-listener#39 that fixed issues using ES6/harmony promises with the newrelic client.
Since async-listener is often brought in via cls, it would be helpful if cls would up its async-listener dependency to include 0.5.3 by default instead of relying on semver fuzzy matching
Could the async-listener dependency be updated to get the new release?
Just curious if this is expected behavior, or not. I'm still going through the tests to see if you've defined it.
Binding to multiple namespaces is not orthogonal, it seems the second namespace affects the first.
'use strict';
var events = require('events')
, cls = require('continuation-local-storage')
;
var ns1 = cls.createNamespace('1');
var ns2 = cls.createNamespace('2');
var dd = new events.EventEmitter();
// emulate an incomign data emitter
setTimeout(function () {
dd.emit('data', 'hi');
}, 100);
ns1.set('name', 'tom1');
ns2.set('name', 'tom2');
ns1.bindEmitter(dd);
// binding ns2 affects the ns1 binding
ns2.bindEmitter(dd); // COMMENT OUT AND RUN
ns1.run(function () {
ns1.set('name', 'bob');
dd.on('data', function (data) {
console.log( 'EventEmitter', ns1.get('name') );
});
});
Erasing the ns2.bindEmitter
will cause the above to log bob
.
The way I've structured this in my head, I'm surprised by this behavior. I wouldn't expect namespaces to interfere with each other.
I am having an interesting issue. CLS was created for commerical pursuits for newrelic. I have newrelic and like it. You gracefully open sourced the app. I want to use CLS and newrelic. Each time I include both newrelic and CLS the newrelic library wipes my CLS stores out. There are no naming conflicts.
HTTP requests from node seem to loose the context.
Is there no async wrapper around http to keep context?
I tried this, didn't work either:
...
var ns = process.namespaces.app;
result= yield function(callback) {
request({
url: `${config.elastic.host}/${db}/${type}/_search`,
method: 'POST',
json: true,
body: queryBody
}, function(error, response, body) {
ns.bind(callback)(error, body);
});
};
Do I miss anything?
If the code uses bindEmitter
, and the emitter triggers after context.exit()
, should the emitter work?
For example, the code:
app.use(function*(next) {
var context = ns.createContext();
ns.enter(context);
ns.bindEmitter(this.req);
ns.bindEmitter(this.res);
try {
ns.set('context', this);
yield* next;
} finally {
ns.exit(context);
}
});
If this.res
emits an event after ns.exit
, this event will normally be handled in the context?
I'm asking that, because it must really help to track dead ends in the app. Must know whether this should work or not.
Hi Forrest,
First of all, in my view cls is a wonderful peace of software. I just started using it and it solves lots of issues for me. Thank you for creating it and sharing it.
This is actually a questing rather than an issue. I've successfully integrated cls in an Express 4 application but after adding session middleware I started experiencing issues. To be more specific, the context gets lost. The code I've got is very similar to the following:
var express = require('express');
var session = require('express-session');
var cls = require('continuation-local-storage');
var ns = cls.createNamespace('contextNamespace');
var app = express();
app.use(function(req, res, next) {
ns.bindEmitter(req);
ns.bindEmitter(res);
ns.run(function() {
next();
});
});
app.use(function(req, res, next ) {
// context is available
var namespace = cls.getNamespace('contextNamespace');
next();
});
// this is the critical session middleware
app.use(session({ resave: true, saveUninitialized: true, secret: 'someSecret', store: sessionStore }));
app.use(function(req, res, next ) {
// context is lost
var namespace = cls.getNamespace('contextNamespace');
next();
});
As per the comments in the code above, the content gets lost for all middleware after app.use(session());
I went through all open and closed issues and I gather that in this kind of situations ns.bind() can be used but I don't quite understand how. So, I was wandering if you could give me some directions as to how to approach and solve this issue?
Thank you in advance.
I've got one other minor note. In the very first code example in your README.md, you have session.set('user', user);
which doesn't seem to be enclosed in a session.run()
. Is this OK or my understanding of cls is still too shallow?
Cheers,
Nasko.
I'm trying to use the module with Koa.JS.
For example, I want to pass a request id to nested execution:
var createNamespace = require('continuation-local-storage').createNamespace;
module.exports = function* requestHandler(next) {
this.reqId = Math.random();
var request = createNamespace('request');
request.run(function*() {
request.set('reqId', this.reqId);
yield next;
});
};
The code above is like an example from CLS docs, but it doesn't work. Probably, because request.run
doesn't expect a generator function and actually doesn't run it.
How do I use CLS here?
https://github.com/othiym23/node-continuation-local-storage/blob/master/context.js#L52
message: Cannot assign to read only property 'error@context' of illegal access
stack: TypeError: Cannot assign to read only property 'error@context' of illegal access
at ServerResponse. (/node_modules/continuation-local-storage/context.js:77:31)
With AsyncWrap taking shape, it feels like it could be a good time to determine how CLS (or a CLS-like module that uses AsyncWrap) should work with promises.
After delving into how promises and CLS interact, I've come to the conclusion that there are 3 different ways in which a CLS/Promise shim can work.
All three approaches have their own logic, and they're all incompatible with each other.
It's not clear which is the "correct" way. The behaviour of native promises with CLS (through the shim provided by async-listener) follows one convention, cls-q and cls-bluebird follow another.
Here are the 3 different approaches:
This is the behavior of native JS Promises.
The context at the end of the last .then()
callback is maintained for the next .then()
callback. Where and when the .then()
is added to the promise chain is irrelevant.
For CLS purposes, the following are treated the same:
fs.readFile('foo.txt', function(text) {
console.log(text);
});
fs.readFile('foo.txt').then(function(text) {
console.log(text);
});
i.e. promises are essentially sugar for callbacks, rather than a distinct syntax with different behavior.
If the code inside a .then()
callback loses CLS context (due to using a queue or similar), then the shim would NOT correct this.
On the positive side, it allows a CLS context to be created within a .then()
callback and the rest of the promise chain that follows runs within that context. This could be useful e.g. for middleware.
Promise.resolve().then(function() {
return new Promise(function(resolve) {
ns.run(function() {
ns.set('foo', 123);
resolve();
});
});
}).then(function() {
console.log(ns.get('foo')); // prints 123
});
CLS context is set at the time of the promise's creation. Any promises which chain on from another promise inherit the same context.
This is the same as (1) except:
.then()
callback loses context, context is restored for the next .then()
callback.then()
callback, it is NOT maintained for the next .then()
in the promise chainns.run(function() {
ns.set('foo', 123);
Promise.resolve().then(function() {
return loseContext(); // returns a promise, but loses CLS context
}).then(function() {
// original CLS context has been restored
console.log(ns.get('foo')); // prints 123
});
});
var promise;
ns.run(function() {
ns.set('foo', 123);
promise = Promise.resolve();
});
ns.run(function() {
ns.set('foo', 456);
promise.then(function() {
console.log(ns.get('foo')); // prints 123
});
});
CLS context for execution of .then()
callback is defined at time .then()
is called. This is not necessarily the same context as the previous promise in the chain.
Similarly to (2), if a .then()
callback loses context, this doesn't affect context for the next .then()
in the chain.
This appears to be the convention followed by cls-q and cls-bluebird.
var promise;
ns.run(function() {
ns.set('foo', 123);
promise = Promise.resolve();
});
ns.run(function() {
ns.set('foo', 456);
promise.then(function() {
console.log(ns.get('foo')); // prints 456
});
});
The following code demonstrates the difference between the 3 conventions. It will log "This Promise implementation follows convention X", where X depends on which approach the promise shim takes.
var promise;
ns.run(function() {
ns.set('test', 2);
promise = new Promise(function(resolve) {
ns.run(function() {
ns.set('test', 1);
resolve();
});
});
});
ns.run(function() {
ns.set('test', 3);
promise.then(function() {
console.log('This Promise implementation follows convention ' + ns.get('test'));
});
});
NB With native JS promises you get "This Promise implementation follows convention 1". With cls-q or cls-bluebird you get "This Promise implementation follows convention 3".
I think this is debatable. It depends on how you conceptualize promises and the control flow they represent.
Convention 1 is the simplest and isn't opinionated about what a promise control flow represents.
Native JS Promises follow this convention, so there's an argument other promise shims should follow the same convention to avoid confusion.
This doesn't cover the common use case of patching where a library like redis
loses CLS context within it. However, there's a strong separation of concerns argument that a shim for a promise library should just shim the promise library. If another library loses CLS context, then that library should be shimmed. i.e. solve the problem that redis
loses context with cls-redis
not cls-bluebird
!
Convention 2 conceptualizes a promise chain as a set of connected actions.
Imagine multiple tasks running in parallel, each composed of multiple steps e.g. read a file, transform it, write it out again. Each task run is represented by a promise chain.
Now if you want to add an extra step to each of the tasks (e.g. notify a server when task is done), you'd add an extra .then()
to the end of the promise chain for each task. You would expect each callback to run in the CLS context for that task.
Convention 3 conceptualizes a promise chain as a queue.
Imagine a resource which can only be accessed by one process at a time. The queue for access is represented by a promise. When a process finishes accessing the resource, it resolves the promise and the next in the queue (next promise in the chain) then starts up. If you want access to the resource, you add a .then()
to the promise chain.
If a running task (e.g. serving an HTTP request), gets the resource and then continues on with other things, you would expect promises chained on after accessing the resource to execute in the CLS context of the task, NOT the context of the preceding item in the resource queue.
function Resource() {
this.promise = Promise.resolve();
}
Resource.prototype.read = function() {
this.promise = this.promise.then(function() {
return fs.readFileAsync('/path/to/resource'); // NB returns promise
});
return this.promise;
};
var resource = new Resource();
// somewhere else in code
function doTheDo() {
return resource.read().then(function(resourceContent) {
// do something with the resource's content
});
}
I'm not pushing for one convention over another. I just thought it'd be useful to lay out what I think are the 3 different choices and their implications.
What I do suggest is that if there's some consensus on which convention is best, this be set out in a set of tests, so everyone can be sure that the cls-promise implementation they're using is compliant.
It would also clear up what's a bit of an ambiguity - there's been some confusion for example here: TimBeyer/cls-bluebird#1 (comment).
I've made a start on a test suite here: https://github.com/overlookmotel/cls-bluebird-test
Anyone have any thoughts on this?
This example code is net-events.tap.js modified to use bindEmitter.
The socket 'data' handler only has state preserved when the 'data' handler is registered before the bindEmitter call.
When the socket 'data' handler is registered after the bindEmitter call, the state is not preserved.
There is a register_handler_after_bindEmitter var that can be modified to see the behavior change.
Is this as designed? Thank you.
(node v0.10.24, continuation-local-storage-3.1.1)
'use strict';
var net = require('net')
, tap = require('tap')
, test = tap.test
, createNamespace = require('../context').createNamespace
;
test("continuation-local state with net connection", function (t) {
t.plan(4);
var namespace = createNamespace('net');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
var server;
namespace.run(function () {
namespace.set('test', 0x1337);
server = net.createServer(function (socket) {
t.equal(namespace.get('test'), 0x1337, "state has been mutated");
socket.on("data", function () {
t.equal(namespace.get('test'), 0x1337, "state is still preserved");
server.close();
socket.end("GoodBye");
});
});
server.listen(function () {
var address = server.address();
var client = net.connect(address.port, function () {
namespace.run(function () {
function register_handler() {
client.on("data", function () {
t.equal(namespace.get("test"), "MONKEY", "state preserved for client data");
t.end();
});
};
namespace.set("test", "MONKEY");
var register_handler_after_bindEmitter = false;
if (!register_handler_after_bindEmitter) register_handler();
namespace.bindEmitter(client);
if (register_handler_after_bindEmitter) register_handler();
t.equal(namespace.get("test"), "MONKEY",
"state preserved for client connection");
client.write("Hello");
});
});
});
});
});
});
Although it looks like the module should support it, you cannot interleave contexts.
var cls = require('continuation-local-storage');
var ns = cls.createNamespace('test');
var ctx1 = ns.createContext();
var ctx2 = ns.createContext();
ns.enter(ctx1);
ns.enter(ctx2);
ns.exit(ctx1);
ns.exit(ctx2);
This results in a AssertionError: context not currently entered; can't exit
A variant of this also seems to fail:
var cls = require('continuation-local-storage');
var ns = cls.createNamespace('test');
var ctx1 = ns.createContext();
ns.enter(ctx1);
var ctx2 = ns.createContext();
ns.enter(ctx2);
ns.exit(ctx1);
ns.exit(ctx2);
Hello there. After the async-listener
update that happened a day ago, just require
ing or importing
continuation-local-storage
hangs indefinitely for me now.
I am currently working around this problem by npm shrinkwrap
ing and pegging async-listener
to 0.5.6
.
Returned to check this module after some time.
Is it supposed to work with mongoose/mongodb out-of-the-box now? Guess, not?
MongoDb driver has a fix for domains inside. Maybe CLS could integrate w/ domains to use this fix too?
Please see below code:
function FunctionB_Callback() {
// ...
// We cannot get the tag from namespace in this callback.
}
function FunctionA_Callback() {
// ...
// We can get the tag from namespace in this callback.
FunctionB.callMethod(FunctionB_callback);
}
{
// Codes to get a namespace ...
// ...
namespace.run( function(context) {
context.tag = "Added in global"; // This tag can be found in FunctionA_Callback
FunctionA.callMethod(FunctionA_Callback);
});
}
As we know, FunctionA.callMethod is called in the scope of the namespace and the tag variable can be access in its callback FunctionA_Callback. But it seems embedded functions are not supported: FunctionB.callMethod and its callback FunctionB_callback can never find the tag variable.
Is it an expected behavior? Are there any solutions to support embedded function calls?
Thanks a lot!
I'm having troubles with configuration under express4. I tried at the beginning and in the end with same result. I followed the instructions of issue #15
This is the code I tried:
app.use(function(req, res, next) {
ns.bindEmitter(req);
ns.bindEmitter(res);
return ns.run(next);
});
app.use(function(req, res) {
return ns.set('traceId', 'something');
});
app.get('/traced', function(req, res) {
console.log("traceId = " + (ns.get('traceId')));
res.send('ok')
});
And the stack error:
TypeError: Object object has no method 'toString'
at node_modules/express/lib/application.js:140:28
at node_modules/express/lib/router/index.js:140:5
at node_modules/express/lib/router/index.js:265:10
at next (node_modules/express/lib/router/index.js:165:14)
at next (node_modules/express/lib/router/index.js:182:38)
at next (node_modules/express/lib/router/index.js:182:38)
at next (node_modules/express/lib/router/index.js:182:38)
at next (node_modules/express/lib/router/index.js:182:38)
at next (node_modules/express/lib/router/index.js:182:38)
at next (node_modules/express/lib/router/index.js:182:38)
I thought it would be worth talking about the logic behind getNamespace
returning null.
Are there cases where that is desired behaviour vs. simply throwing at that point?
https://github.com/othiym23/node-continuation-local-storage/blob/master/context.js#L175
Would a cleanup command be useful..? When first playing with the API, I mistakenly started making namespaces a plenty, but then noticed they never disappeared. Should they have some kind of lifecycle..?
I'm narrowing in on the case of the undefined property. We've seen a few stack traces like this:
TypeError: Cannot read property 'context@__NR_tracer' of undefined
at prepare (/Users/jacob/Projects/continuation-local-storage/context.js:123:32)
at EventEmitter.punched [as emit] (/Users/jacob/Projects/continuation-local-storage/context.js:155:29)
at Object.<anonymous> (/Users/jacob/Projects/continuation-local-storage/event.js:20:4)
Which I can reproduce as follow:
var events = require('events');
var cls = require('continuation-local-storage');
var n = cls.createNamespace('__NR_tracer');
var ee = new events.EventEmitter();
function H1() {
console.log(1);
}
n.bindEmitter(ee);
ee.on('test', H1);
ee.on('test', H1);
var x;
ee._events['test'][1] = x;
ee.emit('test');
I have to directly fiddle with the event emitters events to reproduce the error, but it's definitely raising the right error.
Also I noticed in node 0.8.25 there is no error thrown, the app just silently exits.
If I want to use a CLS context on part of an HTTP request, say just for a single Express middleware, is there a way to exit the active namespace before calling next()
?
'use strict'
let koa = require('koa');
let cls = require('continuation-local-storage');
let nameSpace = cls.createNamespace('app.context');
var knex = require('knex')({
"client": "mysql",
"connection":{
"host": "localhost",
"user": "test",
"password": "123",
"database": "testBase",
"port": "3006"
},
"pool":{"min":1, "max":200}
});
var app = koa();
app.use(function*(next){
yield new Promise(nameSpace.bind(function (resolve) {
resolve();
}));
nameSpace.set('token', 123);
yield next;
})
app.use(function * () {
let token;
token = cls.getNamespace('app.context').get('token');
console.log(token);
//123
yield knex.select(knex.raw('1'));
token = cls.getNamespace('app.context').get('token');
//undefined
console.log(token);
});
app.listen(3000);
Just as an FYI, we're in the process of migrating from Q
to bluebird
so a new shim was needed.
I did a quick port from cls-q
, only adapting the tests slightly.
It can be found at https://github.com/TimBeyer/cls-bluebird
The existence of the default/global context has the potential to be incredibly dangerous.
Using the example from the readme, imagine you are setting a user's session data in CLS and then reading it somewhere else in order to establish, let's say, permissions for a resource. Here is an example of a normal request cycle:
Request arrives -> User session is set -> Other stuff happens -> User session is read -> Permissions are checked -> Response is sent
The problem is, if you were to introduce code that makes your callback chain lose it's context (for example, you use Q and don't make use of the cls-q workaround) you would end up writing to and reading from the global context. This is bad for obvious reasons, but now here is the best bit, this is probably not going to be apparent in a development environment, because you will usually not be making concurrent requests, and so long as the above flow all happens in sequence, your app will appear to be functioning correctly, but what happens when you have concurrent requests being processed? Here is that flow, with broken callback chains that are using the global context:
UserA arrives -> UserA session is set in global context -> UserB arrives -> UserB session is set in global context -> UserA reads session from global context, get's UserB's session -> your whole product disappears up it's own rear end
Is there actually a reason for the global context to exist? Given that the purpose of this module is to provide thread local storage, it seems like having a global context is only ever going to be dangerous, why would you ever want to store something in CLS globally? Why wouldn't you just use a global for that?
The CLS context seems to be lost during async waterfall or series functions. It does appear to be working with parallel.
Tested with
node 5.0.0
and node 5.3.0
cls 3.1.4
async 1.5.0
Here is a simple test I've setup which fails on the 2nd waterfall function, the context is available within the 1st waterfall function. Am I doing something incorrect or is there an issue here?
var async = require("async");
var should = require("should");
var cls = require("continuation-local-storage");
describe("API Context", function () {
context("Test CLS",
function () {
it("it should provide context across async.waterfall flow",
function (done) {
var ns = cls.createNamespace("test1");
var expectedDate = new Date();
ns.run(function () {
ns.set("currentDate", expectedDate);
async.waterfall(
[
function (next) {
ns.get("currentDate").should.be.eql(expectedDate);
next();
},
function (next) {
ns.get("currentDate").should.be.eql(expectedDate);
//cannot get access to context here
next();
}
],
function (err) {
ns.get("currentDate").should.be.eql(expectedDate);
//cannot get access to context here
done();
}
)
})
}
);
});
});
This may be just a documentation issue, but does this library currently work? I must be missing something, but I can't figure out how to call it from reading README.md. There are inconsistencies in the examples that beg the following questions:
1. do I need to call ns.run() or ns.createContext() - are they the same?
2. do I need to call ns.enter() and ns.exit()? I've not been able to ever retrieve data from my namespace unless I do call ns.enter().
3. should I add data to the namespace with ns.set('field', value) or context.field = value? The former seems to not work.
4. when I need to pull data out of the namespace, should I call cls.getNamespace() each time, or should I do it once at the top of each .js source file?
The best I have figured out how to do so far is if I answer my questions as follows:
1. doesn't matter - use ns.run() or ns.createContext()
2. yes, you HAVE to call ns.enter() (and ns.exit()?)
3. ns.set('field', value) does not work - use context.field = value
4. doesn't matter - call cls.getNamespace() once or each time
I've tried using the library this way with node versions 0.10.22 and 0.10.24 vm and I can't get things working right. Here is the code I have in my express.js app (my app is coffee-script in case that makes a difference, though it shouldn't):
at app startup:
cls = require('continutation-local-storage') // at the top of the source file
ns = cls.createNamespace('catalogRequest')
when a request comes in:
ns.run (ctx) => // => is coffee-script for function()
ns.enter(ctx)
ctx.traceId = traceId
<asynchronous call chain> =>
ns.exit(ctx)
in a function called later in the continuation chain:
cls = require('continutation-local-storage') // at the top of the file
ns = cls.getNamespace('catalogRequest')
logger.info("traceId = #{ns.get('traceId')}")
This call to logger.info() does manage to print out the traceId that I set when I received the request - apparent success. But I still have two problems:
1. if I submit two requests to my service, one after the other, the traceId logged for the second request doesn't change. The namespace retrieves the first value I set on it (for the first request) forever.
2. in another source file - one in a different package that is included as a library - any call to cls.getNamespace('catalogRequest') always returns null. I can't get access to the namespace at all.
Hi, I’m looking into using Sequelize’s continuation-local-storage support. I don’t want to have to decide on a name for the namespace because I don’t like coming up with names and I would probably choose something stupid and generic which might conflict with code which I haven’t written.
I have no need to ever call getNamespace()
. I can access the return value of createNamespace()
from all code where the README would suggest I should call getNamespace()
, so what’s the point of that function when I have an object reference available?
I would like to be able to something like the following to get automatic transaction binding through Promise chains whenever I use the sequelize
object:
var clsNamespace = require('continuation-local-storage').createNamespace();
var Sequelize = require('sequelize');
Sequelize.cls = clsNamespace;
var sequelize = new Sequelize('sqlite:file.sqlite');
but this fails with AssertionError: namespace must be given a name!
thrown by [email protected]. I don’t get it—what insurmountable technical obstacle to supporting anonymous namespaces requires me to use a name here? Could that be documented along with a workaround (i.e., a way reliably generate unique namespace names) if it isn’t going to be fixed?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.