An introduction to race condition

An introduction to race condition

A race condition is a type of software bug that occurs when multiple threads or processes access a shared resource simultaneously, and the outcome of the execution depends on the timing of these accesses.

In Ruby, race condition can occur in multi-threaded code, as well as in code that uses the Ruby on Rails framework. This article will explore the different examples of race condition in Ruby on Rails code and how to avoid them. The most common examples of race condition are lost-update, and check-then-act.

Lost-update

One common example of a race condition in Ruby on Rails is the “lost update” problem. This occurs when multiple threads or processes update the same database record simultaneously, and the changes made by one thread or process are overwritten by the changes made by another.

def increment_image_views(image_id)
  image = Image.find(image_id)
  image.views += 1
  image.save
end

Consider the above example, if two threads or processes execute this code simultaneously, they may both read the same value for the image views, increment it, and then save it back to the database. The second process to save the views counter will overwrite the changes made by the first, and the final value of image views will be one higher than it should be.

Check-then-act

This occurs when multiple threads or process check the state of a shared resource and then take action based on that state, but the state of the resource may change between the time the check is made and the action is performed.

def transfer_money(from_account, to_account, amount)
  if from_account.balance >= amount
    from_account.balance -= amount
    to_account.balance += amount
    from_account.save
    to_account.save
  end
end

How to avoid a race condition

To avoid the above mentioned examples of race condition, one can use the ActiveRecord::Base.transaction method which wraps the database operation in a transaction and ensures that the updates are atomic and that no other threads or processes can access the record until the transaction is complete.

def transfer_money(from_account, to_account, amount)
  ActiveRecord::Base.transaction do
    if from_account.balance >= amount
      from_account.balance -= amount
      to_account.balance += amount
      from_account.save
      to_account.save
    end
  end
end

Another way to prevent race conditions in Ruby on Rails is to use lock! method. It uses a mechanism called pessimistic locking to prevent race condition. When a transaction acquires a lock on a specific row using lock!, other transactions are prevented from accessing and modifying that row until the lock is released. This ensures the consistency of data.

def increment_image_views(image_id)
  image = Image.find(image_id)
  image.lock!
  `or`
  image = Image.lock.find(image_id)
  image.views += 1
  image.save
end

In conclusion, race condition can be a problem in Ruby on Rails when multiple requests try to access and update the same database record at the same time. To prevent a race condition, developers can use database transactions or the lock! method to ensure that only one request can update the record at a time. It is crucial to weigh the performance impact of methods used to avoid a race condition.

Happy coding!!

Get the book