passing context along an event chain

326 views
Skip to first unread message

Bruno Jouhier

unread,
Oct 13, 2010, 3:15:15 AM10/13/10
to nodejs
I'd like to be able to pass a piece of context information to all the
callbacks of an event chain. My main use case is localization: my
server needs to format its end user messages in different locales for
different users.

One way to do it would be to pass an explicit context object. But this
is problematic with low level calls that format data. I would have to
pass the locale explicitly to all the toString() methods (and thus all
their callers which may be in different modules). This means a lot of
parameter passing overhead.

What I would like to have is the equivalent of TLS (Thread Local
Storage) in node, i.e. the ability to set the locale in some kind of
"event context" and then retrieve it from the "event context", from
any callback.

This would also allow me to solve a tricky problem with error
handling. If something fails inside I/O code from a third party
library, I can only trap by catching uncaughtException events. But
then, I have completely lost the context. I can log the error but I
cannot correlate the exception to the HTTP request that caused it, and
send an appropriate error response. If I had an "event context", I
could solve this problem.

Any thoughts?

Also, it would be nice to have some kind of standard API to access the
locale from the context, so that localized libraries developped by
different people work well together.

Isaac Schlueter

unread,
Oct 13, 2010, 4:03:08 AM10/13/10
to nod...@googlegroups.com
On Wed, Oct 13, 2010 at 00:15, Bruno Jouhier <bjou...@gmail.com> wrote:
> One way to do it would be to pass an explicit context object. But this
> is problematic with low level calls that format data. I would have to
> pass the locale explicitly to all the toString() methods (and thus all
> their callers which may be in different modules). This means a lot of
> parameter passing overhead.

I guarantee you, passing parameters is not your bottleneck. But there
are more convenient ways to do this, for sure.

> What I would like to have is the equivalent of TLS (Thread Local
> Storage) in node, i.e. the ability to set the locale in some kind of
> "event context" and then retrieve it from the "event context", from
> any callback.

If this is in the context of an HTTP server, I'd suggest either
putting it on the request object, or perhaps creating a sessions
object and giving users a session token of some kind (cookie, etc.)


> This would also allow me to solve a tricky problem with error
> handling. If something fails inside I/O code from a third party
> library, I can only trap by catching uncaughtException events. But
> then, I have completely lost the context. I can log the error but I
> cannot correlate the exception to the HTTP request that caused it, and
> send an appropriate error response. If I had an "event context", I
> could solve this problem.

This is a much stickier wicket than just a way to store localization
data in a session.

--i

JeanHuguesRobert

unread,
Oct 13, 2010, 7:06:52 AM10/13/10
to nodejs
This is a tricky problem.

Because there is a single thread, maybe you can get by in some cases
with some global context.

// contexted.js
//
// Bind a global context to a function

function contexted( f ){
var ctx = contexted.scope
var new_f = function(){
contexted.scope = ctx
return f.apply( this, arguments)
}
new_f.scope = ctx
new_f.scope.callee = f
return new_f
}

Usage:
contexted.scope = {locale:"Earth"} // This is the "global" context
to attach
var cb = contexted( function(){
Sys.puts( "Hello " + contexted.scope.locale) // This is where it
is used
})
somesource.addCallback( cb) => "Hello Earth" when cb fires
function uncaughtExceptionHandler(){
Sys.puts( "Last contexted scope " +
Sys.inspect( contexted.scope))
...
}

Alas, this solve your problem only if the proper "contexted" function
is invoked prior to the I/O third party code bombing.


Regarding some "standard API"

Something cheap would be a NodeJS managed global variable holding the
current event and its associated "source object". Well behaved
libraries would have to provide some get/setClientData on their
objects. That way an application could figure out the context by
exploring these client data when handling an event.

Bradley Meck

unread,
Oct 13, 2010, 11:42:17 AM10/13/10
to nodejs
I would argue against a global event/emitter as sometimes emitters do
not want to be visible to exterior code. As well as that is a slow
down for the event loop which is extremely hot code.

On Oct 13, 6:06 am, JeanHuguesRobert <jeanhuguesrob...@gmail.com>
wrote:

JeanHuguesRobert

unread,
Oct 13, 2010, 9:04:23 PM10/13/10
to nodejs
On Oct 13, 5:42 pm, Bradley Meck <bradley.m...@gmail.com> wrote:
> I would argue against a global event/emitter as sometimes emitters do
> not want to be visible to exterior code. As well as that is a slow
> down for the event loop which is extremely hot code.On Oct 13, 5:42 pm, Bradley Meck <bradley.m...@gmail.com> wrote:
> I would argue against a global event/emitter as sometimes emitters do
> not want to be visible to exterior code. As well as that is a slow
> down for the event loop which is extremely hot code.

I certainly agree, performance matters. I was not thinking about an
event "emitter" actually, just about some global "Event.source"
holding the "this" that callbacks receive.

Using this "Event.source" Bruno could find the locale using
Event.source.brunoLocale (assuming source is an HttpRequest that was
previously assigned a brunoLocale attribute).

... or actually using some (better) Event.context.brunoLocale, because
the idea of something equivalent to Thread Local Variables in an
evented environment is nice I think (for as much as anything even
slightly "global" can be considered "nice" that is).

For example when a FS.readFile()'s callback is called, one would
usually like to restore the context captured when FS.readFile() was
called.

Elaborating on my previous "contexted" proposal:
FS.readFile( xxx, this.contexted( function( err, data ){
xxxxx using global contexted.scope (& a proper "this")
}))
Where
var contexted = Object.prototype.contexted = function( f ){
var ctx = contexted.scope
var that = this
return function(){
contexted.scope = ctx
return f.apply( that, arguments)
}
}

But 1/tedious 2/slow

A solution where both a global Event.source and a global Event.context
is managed by NodeJS seems much more efficient to me. About 3 or 4
assignments per event. So the impact on performance should be close to
zero.

Please note that this also solve the issue with bombing third party
code, assuming the uncaughtExceptionHandler exceptionally gets the
Event.context that was active when the exception occurred, instead of
the context as it was when the handler was registered, as would
normally happen.

In the debate events vs threads, Event.source might be equivalent to
the Thread.current that most (all?) thread libraries provide,
Event.context being similar to Thread Local Variables.

Bruno Jouhier

unread,
Oct 15, 2010, 4:50:43 AM10/15/10
to nodejs


On Oct 14, 3:04 am, JeanHuguesRobert <jeanhuguesrob...@gmail.com>
wrote:
> On Oct 13, 5:42 pm, Bradley Meck <bradley.m...@gmail.com> wrote:
>
> > I would argue against a global event/emitter as sometimes emitters do
> > not want to be visible to exterior code. As well as that is a slow
> > down for the event loop which is extremely hot code.On Oct 13, 5:42 pm, Bradley Meck <bradley.m...@gmail.com> wrote:
> > I would argue against a global event/emitter as sometimes emitters do
> > not want to be visible to exterior code. As well as that is a slow
> > down for the event loop which is extremely hot code.
>
> I certainly agree, performance matters. I was not thinking about an
> event "emitter" actually, just about some global "Event.source"
> holding the "this" that callbacks receive.
>
> Using this "Event.source" Bruno could find the locale using
> Event.source.brunoLocale (assuming source is an HttpRequest that was
> previously assigned a brunoLocale attribute).
>
> ... or actually using some (better) Event.context.brunoLocale, because
> the idea of something equivalent to Thread Local Variables in an
> evented environment is nice I think

I think that this would solve my problem. Here is how I understand a
possible implementation:

* We introduce a global Event.context variable that holds the context.
The context is an object that holds all the TLS data.
* We introduce an eventContext property in the EventEmitter class.
* In the constructor of EventEmitter, we assign the global
Event.context to this.eventContext
* In EventEmitter.prototype.emit, we assign this.eventContext to the
global Event.context before invoking the callbacks.

Given this, I should be able to allocate a context in my node
dispatcher and assign it to both Event.context and
request.eventContext. And I should be able to access this context from
any of my callbacks through the global Event.context variable.

This should work for all the event emitters that my code will create
as they will hold the correct event source and will set the global
before invoking callbacks.

But this won't work "as is" for global event emitters. So, it won't
solve my "uncaughtExeption" issue. But what if we use
process.eventContext as global, instead of a separate Event.context
variable? Then, uncaughtException would also be dispatched with the
right context.

But will this really work? Here, I am assuming that all async
callbacks go through an event emitter and an emit call. Is this really
the case ? (I'm only starting to investigate node's internals and I
have the impression that callbacks don't necessarily go through an
emitter). If this is not the case, then this logic would probably need
to be handled at a lower level inside node. Trickier but I don't see
why this could not be done.

> (for as much as anything even
> slightly "global" can be considered "nice" that is).

Global variables have obviously a bad reputation (and they deserve it
unless immutable). But thread local variables are a different story.
Their main use is to hold information about the agent who is
performing the operation: his identity, his locale, his timezone
(although most frameworks don't seem to care much about handling
agents in different timezones), etc. This is information that we
sometimes need in low level modules (date and time formatting for
example) but that we don't want to pass explicity. So, there is a real
use case here.

>
> For example when a FS.readFile()'s callback is called, one would
> usually like to restore the context captured when FS.readFile() was
> called.
>
> Elaborating on my previous "contexted" proposal:
>   FS.readFile( xxx, this.contexted( function( err, data ){
>     xxxxx using global contexted.scope (& a proper "this")
>   }))
> Where
> var contexted = Object.prototype.contexted = function( f ){
>   var ctx  = contexted.scope
>   var that = this
>   return function(){
>     contexted.scope = ctx
>     return f.apply( that, arguments)
>   }
>
> }
>
> But 1/tedious 2/slow
>

I agree. This should work but it means a lot of extra code to write,
potential bugs, slower code, etc.

> A solution where both a global Event.source and a global Event.context
> is managed by NodeJS seems much more efficient to me. About 3 or 4
> assignments per event. So the impact on performance should be close to
> zero.
>
> Please note that this also solve the issue with bombing third party
> code, assuming the uncaughtExceptionHandler exceptionally gets the
> Event.context that was active when the exception occurred, instead of
> the context as it was when the handler was registered, as would
> normally happen.
>
> In the debate events vs threads, Event.source might be equivalent to
> the Thread.current that most (all?) thread libraries provide,
> Event.context being similar to Thread Local Variables.

Do we really need two variables? Event.context is probably sufficient,
and I have the impression that we could attach it to the "process"
event emitter, as process.eventContext (but I may be a bit naive about
node here).

Thanks a lot for the response. This is very helpful. Bruno.

r...@tinyclouds.org

unread,
Oct 18, 2010, 7:42:57 PM10/18/10
to nod...@googlegroups.com

This is what the (aging) EventSource branch is attempting to solve.

http://github.com/ry/node/tree/eventsource

Reply all
Reply to author
Forward
0 new messages