Background tasks race conditions

I need to run some cron jobs that clean up a table T which is used and
created by the foreground rails controller. This could naturally lead
to synchronization issues/race conditions etc

eg. Let RC = rails controller. BJ background cron job

RC: Queries T for a record using find
BJ: queries T for the same record and removes it
RC: proceeds as though record still exists

Are there any special methods for handling this?

Thanks

On May 25, 2007, at 11:52 PM, Ra PM wrote:

Are there any special methods for handling this?

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