Race Condition in Laravel: Save yourself from going Bankrupt

Saeid Raei
3 min readJan 7, 2023

Race condition generally is the situation when one thing is being done twice or more at the same time when it’s supposed to be done sequentially, In the tech field, this usually happens when two different processes or threads do something at the same time when they’re not supposed to.

Here’s a pretty typical example in Laravel to demonstrate the situation:

if(!Vote::where('user_id','=',$userId)->exists()){
$vote = new Vote($data);
$vote->save();
}

Hypothesis:

let's assume that logically each user can have one vote but there is no unique key for the user_id column, and instead we have an if statement like what you see above.

Now, what can go wrong with this? we are indeed checking if the user has already voted before creating a new vote right? Here’s where the race condition comes into action.

The Race Condition

Usually, in the first step, people see that an aware user (like a hacker) has multiple votes assigned to him as a result of the race condition.

In the next step, the developer is wondering what went wrong that caused this to happen. Usually, if it’s the first time encountering this issue, it’s hard to find the problem.

Basically what happens is that this aware user sends two requests at the same (almost) exact time, now imagine this piece of code is being run twice at the same time (two different processes):

if(!Vote::where('user_id','=',$userId)->exists()){
$vote = new Vote($data);
$vote->save();
}
  1. Line 1: both processes check if the user has already voted and both of them will result in true statements so the process goes to the next line successfully.
  2. Line 2: each process instantiates a new Vote model.
  3. Line 3: both processes insert that model into the database.

As you can see this aware user was able to submit two different votes but based on our logic this shouldn’t be possible.

The Solution:

Using a unique index, that’s it! other solutions are also possible but the most simple solution for this case is to use a unique key. Generally, the solution depends on the case, and using a unique key is not the ultimate solution for all cases but I would say if it’s possible it would probably be the best solution. also, in this case, we ended up having an index on the user_id column as a bonus point!

Other possible solutions depending on the case might involve things like locking, two-phase commit, database transactions or even creating a whole separate table in the database just for dealing with a race condition.

Why race conditions are hard to deal with?

Race conditions are hard to reproduce and they happen randomly, even if you try to send two requests at the same time one process might finish faster than the other one and the race condition might not occur.
This problem makes us unable to test our application against race conditions easily.

What other types of race conditions should I be aware of?

Other than one user sending multiple requests at the same time, having a lot of concurrent users at the same time is also a big starting point for race conditions. for example, a typical case is when we are caching some expensive computation and due to a race condition between multiple users that happened to send requests at the same time, that expensive computation occurs multiple times concurrently costing a lot of resources.

What can be The Cost?

This problem made a lot of applications vulnerable on a very deep level, and developers might not be aware of them until something strange happens which might be too late. for example, in some cases withdrawing money multiple times at the same time, ended up costing a lot of money (The race condition that led to Flexcoin bankruptcy).

Thank you for reading this article if you found this interesting you might like Top 5 lessons I learned about MySQL the hard way so you don’t have to as well.

--

--