DEV Community

Victor Hazbun
Victor Hazbun

Posted on • Edited on

Best practices: Avoid race conditions 🚘💥🚗😰

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
idl3 profile image
Ernest

Thanks for the tip! There's a typo on the code snippet, I believe you wanted to type with_lock instead of with_block right? :)

Collapse
 
victorhazbun profile image
Victor Hazbun

Right! 😅