background tasks race conditions

You could set up a simple Semaphore table:

class CreateSemaphores < ActiveRecord::Migration

   def self.up      create_table(:semaphores, :id => false, :primary_key => :name) do |t|        t.column :name, :string, :null => false, :limit => 20        t.column :counter, :integer, :default => 1, :null => false      end

     execute("alter table semaphores add constraint pk_semaphores primary key(name)")    end

   def self.down      drop_table :semaphores    end end

and then write a model that gives you locked access to a named thing:

# A basic in-database class. Call ::lock to lock a given name, # ::unlock to unlock it

class Semaphore < ActiveRecord::Base

   # These are the names you can lock

   RESOURCE1 = "Some name"    RESOURCE2 = "Some other name"

   # Attempt to lock the given name by decrementing the count. Return +true+    # if the lock succeeds, +false+ otherwise    def self.lock(name)

     # start by assuming the row exists and is lockable. That gives us an early      # exit 99.99% of the time      count = update_all("counter = counter - 1", [ "counter > 0 && name = ?", name])      return true if count == 1

     # If no row found, might be because there isn't one. Let's try inserting      # it. If it fails, then we know that there was one already (because +name+      # is the primary key) and we can exit. If the insert succeeds, then      # the count is zero, because it now is claimed

     create(:name => name, :counter => 0) rescue false    end

   # Pretty much the opposite of lock... We don't return anything meaningful.    # It's an error to unlock a semaphore that doesn't exist    def self.unlock(name)      count = update_all("counter = counter + 1", [ "name = ?", name])      fail "Unknown semaphore: #{name}" unless count == 1    end

   # Run a block if a semaphore is available, otherwise return false    def self.run_block(name)      if (result = lock(name))        begin          yield        ensure          unlock(name)        end      end      return result    end end

Given that, you could write something like the following in both your controller and your external script

    locked = Semaphore.lock(Semaphore::RESOURCE1) do        mess around        with shared resource     end

   if !locked      flash[:notice] = "Sorry, but the resource was busy..."    end

Dave