Beating Race Conditions in Ruby

By Morten Møller Riis

May 18 2012 12:00 CET

If you have multiple processes or threads performing operations on a file (i.e. managing a plain-text user database) you have to take care of race conditions.

For example if one process read a file, parses it, and saves it again, while another does the same you will probably end up with a corrupt file.

As an example let us take this code:

               File.open("somefile", "a+") do |file|
                 new_content = parse_old_content(File.readlines("somefile"))
                 file << new_content
               end

What we want to do is to lock the file so just one operation on it is allowed.

You could create a .lock file or similar, but the correct way to do this is to use File locking.

A nice method is built into Ruby to allow us to utilize file locking. Consider the following revised code:

               File.open("somefile", "a+") do |file|
                 file.flock(File::LOCK_EX)
                 new_content = parse_old_content(File.readlines("somefile"))
                 file << new_content
                 file.flock(File::LOCK_UN)
               end

#flock is a blocking method so our code will wait for it to obtain the correct file locking for it to continue. Thereby no two processes of this code will write to the file at the same time.

In this example I use LOCK_EX for exclusive locking and LOCK_UN for unlocking. See all the contants in the Ruby API.

Actually, unlocking the file shouldn’t be necessary since it should be unlocked when the file descriptor is closed.