Tag Archives: race condition

0x17 – Atomic science, revisited

I lamented earlier about the absence of a racecondition-free find_or_create implementation and somewhat promised to check back with a working implementation. YES, WE CAN: NOW is the time, folks!

As stated there the first thing missing is an advisory locking implementation. With the database as the natural choice for a synchronisation point that implementation would be DB dependant, the following being a MySQL-implementation:


module ActiveRecord::ConnectionAdapters::MysqlAdapter::AdvisoryLock
  TIMEOUT=10
  
  def locked(lock)
    lock = "#{current_database}_#{lock}"

    begin
      execute "SELECT GET_LOCK(#{quote(lock)},#{TIMEOUT})"
      yield
    ensure
      execute "SELECT RELEASE_LOCK(#{quote(lock)})"
    end
  end
end
  
class ActiveRecord::ConnectionAdapters::MysqlAdapter
  include AdvisoryLock
end

Now, whats still left is a find_or_create implementation. I like that one:


ActiveRecord::Base

class ActiveRecord::Base
  def self.find_first_by_hash(hash)
    find :first, :conditions => hash
  end

  def self.find_or_create(hash)
    find(hash) || connection.locked("#{self.table_name}.find_or_create") do
      find(hash) || create(hash)
    end
  end
end

Where is the “find_or_create_by_attribute_names”, you might ask? Well, I am not too fond of those anyways, because writing


find_by_name_and zipcode name, zipcode

adds several hundred line to the ActiveRecord implementation, doesn’t work with attributes that have an underscore in its name, and saves only a few typestrokes over


find :first, :name => name, :zipcode => zipcode

which could even be reduced down to one by an extended find() implementation, which takes a Hash as a possible first argument.