Here are some scenarios and tips to combat race conditions.
Understanding Race Conditions
A computer program is like a horse race. The computer program does several things at the same time, similarly to how several horses run at the same time in a horse race. Each horse represents what is usually called a thread of execution. That way, one such thread may handle network communication, another one may be responsible for redrawing the user interface. In the case of a race condition, the application works properly if a given horse wins the race. For example, the application may work if horse number five wins, but it will crash if any other horse wins the race. Source https://simple.wikipedia.org/wiki/Race_condition.
Real Life Example: Many users booking the same room at the same time
Imagine you have a booking app, let's pretend you a have Room
model. The rooms
table stores the user_id
.
class Room
def assign_to_user!(user_id)
self.user_id = user_id
self.save!
end
end
It looks fine, right? Nope, if two users make the same reservation at the same time you are probably going to run into serious problems. Here is why..
The first user who submitted the form will open a database transaction and the second user who submitted a millisecond after opened another database transaction. Now we have two opened database transactions, the second transaction has overridden the first. That means, the first user has paid for the room but the second got it, LOL. Just imagine the two hosts discussing in the lobby, you will hear something like this: "No, I booked the room!".
To prevent this you can lock the Room table.
class Room
def assign_to_user!(user_id)
# This block is called within a transaction,
# room is already locked.
with_lock do
self.user_id = user_id
self.save!
end
end
end
By locking the table, even if two requests happened at the same time the record won't be updatable UNTIL the DB lock is released, this is called "Pessimistic locking".
Use Pessimistic locking whenever you know this kind of things are most likely to happen in your app. See full documentation and examples here: https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
Other Scenarios
Here is a small list of cases where you should implement database locking to avoid unwanted race conditions.
- Money transactions
- Booking flights
- Making reservations
- Cryptocurrency transactions
- Gambling
Conclusion & Next Steps
Think about which parts of your application deserve database locking, hopefully you can patch those areas so all your users have a great experience.
Please let me know if you have questions or want to chat about other kinds of database locking mechanisms like Optimistic Locking.
Top comments (2)
Thanks for the tip! There's a typo on the code snippet, I believe you wanted to type
with_lock
instead ofwith_block
right? :)Right! π