Avatar

Introducing race conditions in node.js

03 Jan 2021

I saw a blog post stating that one of the advantages of node.js is

… since node.js is single-threaded, there are no chances of a race condition occurring…

And that seemed like an interesting thing to me. Let’s see if we can introduce a race condition into our node.js code.

The Problem

Let’s try to create a function that only calls another function once.

For the purposes of this example, let’s say we want to create a function incrementOnce that calls another function increment at most one time.

Starting simple

Let’s start with our increment function (Let’s assume it’s an async function, since in a real application this would do some IO)

// script.js
let count = 0;
async function increment() {
	count += 1;
	console.log("count = ", count);
}

increment();
increment();
increment();

console.log("Final count = ", count);

This should output

$ node script.js
count = 1
count = 2
count = 3
Final count = 3

Which is exactly what we’d expect here

Adding our other function

So far so good, but let’s try creating an incrementOnce function that calls our increment function

// script.js
let count = 0;
async function increment() {
	count += 1;
}

let called = false;
async function incrementOnce() {
	if (!called) {
		await increment();
		called = true;
	}

	console.log("count = ", count);
}

incrementOnce();
incrementOnce();
incrementOnce();

console.log("Final count = ", count);

and running it now, we get

$ node script.js
count = 3
count = 3
count = 3
Final count = 3

That doesn’t look like what we wanted. incrementOnce was supposed to call increment once, but it actually ended up calling it 3 times!

What’s going on?

So we use a called flag to make sure that the count is not incremented multiple times. Not only did this not work, the value is 3 in all 3 function calls instead of being 1 , 2 and 3 which is even weirder.

The reason for this is that when we await an expression, we allow another function to execute in the meanwhile.

In our case we’re checking if the called flag is set and calling increment. However we’re calling await on increment, which means other calls to incrementOnce can start executing before the first increment call is complete.

Which means that in this case, each call to incrementOnce sees that called === false, so all of them end going inside the if block.

By the time the called variable is actually set to true in the first call, the other calls have already finished checking the called variable, and they each call the increment function

So when we log the values, all 3 increments have been completed, so they all see the final value of count which is 3

The easy fix

So how do we fix this? The easiest fix for our current snippet is to make sure we update our called flag before we allow the function to be “suspended”, i.e. before await

// script.js
let count = 0;
async function increment() {
	count += 1;
}

let called = false;
async function incrementOnce() {
	if (!called) {
		// Make sure we do this before any awaits
		called = true;

		await increment();
	}

	console.log("count = ", count);
}

incrementOnce();
incrementOnce();
incrementOnce();

console.log("Final count = ", count);

and running it again, we get

$ node script.js
count = 1
count = 1
count = 1
Final count = 1

Voila, it’s fixed

This example may be a bit simplified, but in real life, it’s not unusual to do a few async calls which update the same object. In those cases, if we want to make sure only one update happens we need to make sure we use guards properly.