On Tuesday 19 May 2009, [email protected] wrote:

> Say we have a rails active-record-based model called "instance" which
> can have different states - STOPPED, RUNNING, STARTING_UP,
> SHUTTING_DOWN, etc...
>
> There is a method #start which changes the instance state to
> STARTING_UP only if it is STOPPED.
>
> def start
>   if self.state == STOPPED
>    self.state = STARTING_UP
>    self.save
>    ...
>   end
> end
>
> As you understand if we have multiple concurrent requests for #start,
> there is a possibility of a race condition, where two or more
> processes are running the if branch.
>
> I want the #start request to be atomic, meaning that only one of the
> concurrent processes can actually execute the if branch, others must
> be waiting or get some kind of error.
>
> Should the request be somehow wrapped into a DB transaction?

Yes, that at any rate. However, don't confuse transaction blocks with 
multiple exclusion blocks. A transaction, by itself, doesn't preclude 
concurrent users/processes from reading and updating the same data. The 
potential for conflict arises only through concurrent updates.

The race condition in your unadorned code results from a difference 
between time of check (state == x) and time of change (state = ...). 
There are two strategies to avoid the resulting inconsistencies: 

Optimistic locking, as Fred indicated in another reply. With optimistic 
locking, you run headlong into the race condition, but when writing to 
the database you ensure that it can only succeed if is based on 
consistent data. On updating a record, ActiveRecord checks that the 
updated_at timestamp of the record as currently stored is the same as 
the timestamp when the object was read. An identical timestamp indicates 
that there haven't been any intervening updates.

Pessimistic locking is another way. You can either #lock! an object you 
already have or #find(..., :lock => true) get a locked object to begin 
with. Locking an object like this precludes any changes to the 
corresponding database row until the locking transaction is either 
committed or rolled back.

Rails gives you optimistic locking automatically for tables that have 
the requisite timestamp columns (updated_at). Pessimistic locking you 
have to do explicitly. As a guess, I'd say that pessimistic locking is 
only worth your and the database's effort if conflicts are likely.

At any rate, with both locking strategies you have to take into account 
the possibility of a conflict. With optimistic locking, you get an 
ActiveRecord::StaleObjectError exception in that case. I'm not sure 
about pessimistic locking, but I guess you'll get an indistinctive 
ActiveRecord::StatementInvalid exception.

Michael

-- 
Michael Schuerig
mailto:[email protected]
http://www.schuerig.de/michael/


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to