Code Monkey home page Code Monkey logo

node-continuation-local-storage's People

Contributors

antonmos avatar creationix avatar ericwaldheim avatar gergelyke avatar greenkeeperio-bot avatar groundwater avatar jiaz avatar leedm777 avatar othiym23 avatar overlookmotel avatar qard avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-continuation-local-storage's Issues

Context lost with SetTimeout

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
---------------------------------------------------------
...

Context is lost when request has an error

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'));
        });
});

Expected behaviour with .then() attached to resolved promise

@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?

Lose state for http timeout, ECONNREFUSED, ECONNRESET

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();
});

Is the module production ready?

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?

The problems from my 1+ year experience with CLS

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:

  • There are many modules which do not support CLS. Namely, bluebird, mongoose, a bunch of others. CLS just doesn't work with them or works with serous bugs (ye I know why).
  • Most modules can be monkeypatched to support CLS. But that's only possible if the needed functionality is exported, otherwise I need to fork/really patch it.
  • In practice the monkeypatch tends to break on new major versions of the modules. Then repatching takes time to dive deep into the updated module once again.

The private versions patching problem:

  • Even if I manage to patch something, like bluebird v3, there's a chance that a 3rd-party module in my app uses it's own version of bluebird, say v2, deep inside it's node_modules hierarchy.
  • So I must watch not only over modules I need, but also track any module in the 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.
  • That greatly extends the area of maintenance I need to do to keep everything CLS'able. Also gives so much space for bugs in case if something updates and I miss it.

The dangerous bugs problem:

  • The bugs introduced by a missed CLS-unfriendly updates of a module or it's submodule may be subtle yet really destructive and hard to fix back. Imagine a payment going to a wrong account. Or a regular user getting an admin view of page with all the secret information.
  • That prevents relying on CLS even if though mostly works.

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.

Lost context inside Mongoose query callback function

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?

Alternative for browser and browserify

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?

Does this suffer the same shared EE issue as domains?

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)
})

bindEmitter does not work?

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.

Chained continuations do not receive context

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.

Message passing to parent scope

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!

CLS + cluster

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

Context object does not have Object.prototype as it's prototype

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.

net-events 'close' not preserving state

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();
            });

          });
        });
      });
    });
  });
});

Getting null namespace in function defined in other module from other npm project

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

Allow me to bind a function as a context "root"

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.

Exception on incoming form

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 ?

Binding EEs to Multiple Namespaces

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.

Including newrelic and CLS together overwrites your custom CLS data

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.

Works with http request?

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?

bindEmitter after context.exit should work?

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.

Context gets lost in Express after using session middleware

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.

With generators/promises?

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?

chaos session

use CLS to save session data, but found that session is chaos.
image
image

How CLS should work with promises

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.

The 3 conventions

Here are the 3 different approaches:

Convention 1: Callback style

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
});

Convention 2: Follow promise chain

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:

  • If a .then() callback loses context, context is restored for the next .then() callback
  • If a new CLS context is created within a .then() callback, it is NOT maintained for the next .then() in the promise chain
ns.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
    });
});

Convention 3: Listener attachment context

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
    });
});

Difference between the three

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".

Which way is best?

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
    });
}

Conclusion

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?

[Question] bindEmitter does not preserve state when handler registered before bindEmitter call

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");
          });
        });
      });
    });
  });
});

Cannot Interleave Contexts

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);

Importing lib hangs since async-listener 0.5.7 update

Hello there. After the async-listener update that happened a day ago, just requireing or importing continuation-local-storage hangs indefinitely for me now.

I am currently working around this problem by npm shrinkwraping and pegging async-listener to 0.5.6.

MongoDB doesn't work out of the box - yet?

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?

Values Cannot Be Found in Embedded Function Calls

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!

Can't configure with express 4

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)

Cleaning up / reset

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..?

TypeError: Cannot read property 'context@__NR_tracer' of undefined

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.

  1. we should find out what modules / natural causes this situation can arise
  2. we should make CLS robust against shitty event emitters

Also I noticed in node 0.8.25 there is no error thrown, the app just silently exits.

Is unbind possible?

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()?

Koa NameSpace lost after using Knex

'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);

default / "global" context is potentially very dangerous

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?

Losing Context with Async waterfall

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();
                            }
                        )

                    })
                }
            );
        });
});

documentation ambiguous, and unclear if this library works

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.

Anonymous namespaces

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.