NodeJS and thread-local storage

Craig Taub
3 min readMar 1, 2018

Today we are going to look at the following:
- What is thread-local storage?
- Why does it matter?
- What is continuation-passing style?
- Can you make NodeJS TLS-like?

Something I often think about with NodeJS is how different it is to the languages of my past. Largely Java, PHP and Python (there are some similarities dotted around). I have become so used to it being different that I don’t stop to think about something which is common in other languages but tricky in NodeJS. That being runtime globals, or as other languages call it thread-local storage.

What is thread-local storage (TLS):
It is functionality that allows you to attach and retrieve data to the current processing thread from anywhere at anytime. It works by allocating specific memory to that thread.
In Python it looks like this: threading.local(). Using this you can set/get data that persists for that thread alone.

NodeJS and CPS:
Node and (more explicitly the Express framework) make use of a functional style called CPS (continuation passing-style) to move variables around. Express utilises middleware signatures to pass certain objects all the way through the calling stack. I won’t go into more detail on that here (a great thorough explanation exists from Dr Axel R here) but it’s a great approach to take control of what is going to be called next. Also it is typically easy for an engine to optimise as it makes heavy usage of tail calls.
With Express the typical way to move data throughout a runtime process is to assign to the response locals object e.g. response.locals.myParam = ‘value’;

The problem with this is that you have direct access to the response object. What if you want to set/get some data from a module not connected to middleware or the response object? You have to pass variables around and it can become very troublesome and painful.

There is a great library called Continuation-Local Storage which solves this issue for NodeJS, but today we are going to try and solve it on our own to get an understanding what is going on. First there are a couple of things to be (or get) familiar with.

Event loop:
We all know how great this is. The event loop delegates async actions and processes responses from its queue. All on a single-thread and at a rapid pace.

AsyncListener API:
API available from Node 0.11, allows you to trigger events based on actions in the event loop. Including creation of an event in the queue, the start of processing an event in the queue, and the finish of processing of that event.

LETS GET TO IT !!!

So, using the Event loop and AsyncListener API we can create the following flow of events which ensures that only 1 instance of data (the same instance) is processed at any 1 time inside of a request/thread.

1. Create callback = create() listener fired. Make copy of current storage. e.g. process.nextTick(someFunc).2. Event loop tick = before() listener fired. Load instance of storage into singleton. Execute someFunc.3. After event loop = after() listener fired. Reset storage. Return someFunc.

The above is achieved with the following 2 blocks of code:

1. We create a small singleton that we will export:

export const threadLocals = {
current: {},
set: (key, val) => {
threadLocals.current[key] = val;
},
get: (key) => threadLocals.current[key]
};

2. We attach AsyncListeners handling these thread locals (in the same module).

process.addAsyncListener({
create: () => threadLocals.current,
before: (context, storage) => {
if (storage) {
threadLocals.current = storage;
}
},
after: (context, storage) => {
if (storage) {
threadLocals.current = {};
}
}
});

If you try the above with an application you will see that each request can utilise its own thread-locals. Setting and getting data which will only exist in that request/session/thread.

A simple example exists on my Github here. It creates a small HTTP server with NodeJS and sets/gets session data set from the query string. It delays the response with setTimeout to ensure the globals are not overridden if multiple requests are made simultaneously.

I have had a lot of fun learning this, I hope you got something out of this too. If you feel I have made an error in my logic/understanding or have missed a trick somewhere please let me know. Its all a learning exercise.

Thanks

CRAIG :)

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Craig Taub
Craig Taub