-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
continuation-local-storage without domains? #57
Comments
To be clear, I don't recall that Domain and Zones have ever been discussed in this working group. I can't find any evidence of them being discussed in issues or the meeting minutes (except in the context What you are describing can be done without any clever features, though some frameworks may not favor those programming patterns. But I think we should stay on topic which appears to be continuation-local-storage. continuation-local-storage shouldn't be very hard to implement once
'use strict';
const asyncHook = require('async-hook');
function CLS() {
const ref = new Map();
const self = this;
this.storage = null;
asyncHook.addHooks({
init(uid, handle, provider, parentUid, parentHandle) {
ref.set(uid, self.storage);
},
pre(uid, handle) {
self.storage = ref.get(uid);
},
post(uid, handle) {
self.storage = null;
},
destroy(uid) {
ref.delete(uid);
}
});
asyncHook.enable();
}
CLS.prototype.domain = function (cb) {
const self = this;
process.nextTick(function () {
self.storage = {};
cb();
self.storage = null;
});
} And here is an example with a fake (for simplicity) const cls = new CLS();
function errorHandler() {
setTimeout(function () {
console.error(`user with id ${cls.storage.userId} caused an error`);
}, 10 * Math.random());
}
function showPage() {
setTimeout(function () {
console.log(`hello ${cls.storage.userName} (id = ${cls.storage.userId})`);
}, 10 * Math.random());
}
function requestHandler(userId) {
cls.domain(function () {
cls.storage.userId = userId;
setupSession(function (err) {
if (err) return errorHandler();
showPage();
});
});
}
function setupSession(cb) {
setTimeout(function () {
cls.storage.userName = ['Teodor', 'Suljo'][cls.storage.userId];
cb();
}, 50 * Math.random());
}
requestHandler(0);
requestHandler(1); You can expand on this yourself, and I hereby declare it public domain so you may do with it as you wish. |
@AndreasMadsen this is awesome and thank you for pursuing this. CLS is really all I care about. I refer to Domains as it is (was) the closest officially supported API to provide some kind of context. Before I build some test modules with what you've provided, is the Asyncwrap Issues List up to date? Does async-hook check any more of those boxes, notably promises? 🙏 |
Yes the list is up to date. The |
Thanks @AndreasMadsen for the tips and async-hook. FWIW, I made of version of CLS using AsyncWrap (and async-hook) instead of async-listener called cls-hooked. Knowing it's very beta, I would love to get your thoughts on it. |
Gave it a quick look. It looks correct. I'm not sure where you got Can we close this issue? |
Also I'm not sure why you would need:
is there a bug in one of my modules? :) Anyway, that is a bit off topic. |
Closing, if we need to we cab re-open but CLS is definitely something we're doing our best allow good implementations of. |
Thanks @AndreasMadsen. Yes, if it really should be @Fishrock123 Yes, we can close this for now. I just saw #7565 - it's nice to see the discussions and the momentum remains for AsyncWrap. How do most Node Foundation members see AsyncWrap's purpose - mainly for Tracing and CLS as a pleasant re-purpose for it? I would hope enough would like it to fulfil both Tracing and CLS. The three fundamental use cases of CLS, Tracing and Error Handling often bring developers together around Domains and other features that overlap and seemingly cause conflicting opinions ( Zones, CLS+Promises ) about how Node should support these use cases. IMHO, I don't think we need one component in the middle that satisfies all. Also, if AsyncWrap were to satisfy Tracing and CLS, it moves us one step closer to being able to remove Domains, especially for those who use Promises as a main strategy for Error Handling across async boundaries. |
For tracing purpose, async_wrap is really just valuable for context propagation. It doesn't remove our need to patch things to collect contextual data, only our need to patch things to propagate context, which is just what CLS is supposed to do. Error handling is a whole other thing, and opinions on if it should be attached to async_wrap vary. In my opinion, it is a logical place to connect that issue. But it's a complicated issue, so it's easy for it to become problematic, like domains did. |
Interesting thought. I'm not sure I'm following unless you're describing how CLS handles context and sits on top of AsyncWrap. Regarding patching, it it my hope that with more checkmarks in #29, we would eliminate the need to patch any node and user-land modules. |
async_wrap connects the branches in the call tree, but does not provide any information about what the calls in that tree are. We still need to patch the functions we are interested in to get context about what is actually happening. The need for async_wrap in the tracing domain is purely to ensure accurate context tracking. Patching will not go away, because everyone has different needs for what data should be collected from instrumentation. What would hopefully go away is simply the risk of context loss due to unknown async barriers not getting linked. This unfortunately does not actually get fully solved by async_wrap due to the user-mode queueing problem, but it's a big step to greatly improving the performance and accuracy of that context propagation. IMO, zones is a better solution, but it would have a somewhat larger performance impact, so something like that would likely need to be implemented in userland, on top of something like async_wrap. |
(branched topic and response to #46 (comment))
@rvagg Thanks for chiming in and providing some feedback. I guess I'm really at a loss and seriously need help from those who have done this and what they recommend. I've been programming for almost 20 years in dozens of languages, frameworks, databases and architectures and I'm still not on the same page as many Node foundation members when it comes to the power and importance of having a reliable asynchronous context.
I love Node because it's lightweight but also can be a heavy hitter when needed (scaling). JS on the server and client streamlines data transfers and provides a high level of architectural flexibility and adaptability.
After using Node for 3+ years now, I still need to learn a 'best practice' architectures, design patterns or whatever you want to call it when it comes to passing context.
As a more concrete example, in our case, here's what we need to do:
Multi-tenant Database (our case MongoDB)
Ideally, all of this would be done at the data access layer so developer's can focus on building and adapting to business cases, testing and satisfying user stories.
I don't think these, what I consider enterprise-ish, requirements above are asking a lot and I would expect to be able to build an application in Node that could satisfy them.
At first glance, StrongLoop or Mongoose with middleware plugins appear to be solutions, but they are only as reliable as monkey patched shims can be in providing context over asynchronous boundaries.
How does Express or Hapi help with these requirements?
How have others used Node to satisfy these requirements?
Is there something fundamentally wrong with those requirements?
Any thoughts or guidance would be greatly appreciated.
The text was updated successfully, but these errors were encountered: