Handling Race Conditions

Hi,
I’m new to Prime and wanted to get the community’s thoughts on handling race conditions.

I currently deal with two main race conditions in my application.

  1. Changes to a user’s cash position.

  2. Multiple users trying to enter into the last spot of a contest with a limited amount of spots.

I’m currently using a combination of rails transactions along with isolation environments in postgres to solve this problem. If there is a conflict due to concurrent actions, it will retry the failed changes.

What is the recommended practice for handling race conditions in rails? What is the best way to test race conditions?

Thanks!

+1, very interested in cases such as the one you outlined where it’s not merely a uniqueness constraint.

I don’t have anything brilliant to add here besides: yes, a database transaction.

Here’s how file race conditions are handled:

begin
  FIle.read('/etc/passwd')
rescue Errno::ENOENT => e
  $stderr.puts "No such file: /etc/passwd"
end

It’s important that it’s not this:

if File.exist?('/etc/passwd')
  File.read('/etc/passwd')
end

That’s a classic race condition there, where the file can disappear between the conditional and the #read, leading to the Errno::ENOENT bubbling up and potentially crashing the system.

Similar mindset for example (2) you gave: attempt to do the insert and handle the constraint failure exception. So in Postgres you’d add:

CREATE TABLE entries (
  id SERIAL,
  user_id INTEGER NOT NULL,
  entry_order INTEGER NOT NULL,
  UNIQUE (user_id, entry_order)
  CHECK (entry_order BETWEEN 1 AND 20)
)

(More on StackOverflow, including a trigger: sql - How to write a constraint concerning a max number of rows in postgresql? - Stack Overflow )

Then you simply cannot insert invalid data.

Testing: no guaranteed way to test this from an acceptance/integration level. You can write unit tests that attempt to insert invalid data and make sure the DB doesn’t allow it.

JRuby has thread-weaver ( Google Code Archive - Long-term storage for Google Code Project Hosting. ), which might help you create a reproducable test. Have not tried it.

@Nicolo I think we’ll need to see more code to provide a concrete answer to your specific concerns. The short answer is the transactions are the way to go, as @mikeburns outlines.

However, there may be problem specific solutions that will lead to cleaner, more robust code. For example, the actual changes to user’s cash position are likely backed by a Transaction model. New instances of this model are created for each transaction on a user’s account. And when you ask for the customers balance it looks at the transactions to compute it (on the fly), because transactions never step on each other and the balance is computed on the fly, there is no race condition. For performance improvements, during a period of non-activity you could computer and cache the known balance.

1 Like