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