Why no race condition in Ruby
This is because MRI Ruby threads are not really parallel due to GIL (see here), at CPU level they are executed one at a time.
Each command in a thread is executed one at a time hence @count
in each thread is always updated correctly.
Race condition can be simulated by adding another variable like:
class Counter
attr_accessor :count, :tmp
def initialize
@count = 0
@tmp = 0
end
def increment
@count += 1
end
end
c = Counter.new
t1 = Thread.start { 1000000.times { c.increment; c.tmp += 1 if c.count.even?; } }
t2 = Thread.start { 1000000.times { c.increment; c.tmp += 1 if c.count.even?; } }
t1.join
t2.join
p c.count #200_0000
p c.tmp # not 100_000, different every time
A nice example of race condition is given here, copied below for completeness
class Sheep
def initialize
@shorn = false
end
def shorn?
@shorn
end
def shear!
puts "shearing..."
@shorn = true
end
end
sheep = Sheep.new
5.times.map do
Thread.new do
unless sheep.shorn?
sheep.shear!
end
end
end.each(&:join)
Here's the result I see from running this on MRI 2.0 several times.
$ ruby check_then_set.rb => shearing...
$ ruby check_then_set.rb => shearing... shearing...
$ ruby check_then_set.rb => shearing...
shearing...Sometimes the same sheep is being shorn twice!
Does race condition exist in `puts`?
That's because puts
doesn't write to STDOUT
right away, but buffers the string and writes in bigger chunks.
You can get ruby to write immediately with the following:
STDOUT.sync = true
which should resolve your ordering issue.
How does unique index solve the race condition in validates_uniqueness_of in RoR?
The race condition occurs because rails can't lock down the database and often runs multiple threads at the same time.
To understand how the race-condition occurs, think about what is needed for a uniqueness-check, and I'll give an example of two threads:
Lets suppose Person A and B want to save a new BlogPost with attribute name
which must be unique.
- Person A hits the save button
- Person B hits the save button
- Rails starts thread A that goes and queries the database to see if
there are any BlogPosts with name "My BlogPost" - Rails starts thread B that goes and queries the database to see if there are any BlogPosts with name "My BlogPost"
- Thread A returns "nope, no blogposts with that name, all clear to save"
- Thread B returns "nope, no blogposts with that name, all clear to save"
- Thread A saves the blogpost for person A
- So does Thread B
...and now there's two blogposts with the same name.
There's nothing stopping this from occurring, this is because the "lookup[" and "save" actions are two separate things.. and thus can occur in the manner described above.
However... when you put a unique index on the database... what happens is this:
- Person A hits the save button
- Person B hits the save button
- Rails starts thread A that goes and queries the database to see if
there are any BlogPosts with name "My BlogPost" - Rails starts thread B that goes and queries the database to see if there are any BlogPosts with name "My BlogPost"
- Thread A returns "nope, no blogposts with that name, all clear to save"
- Thread B returns "nope, no blogposts with that name, all clear to save"
- Thread A saves the blogpost for person A
- Thread B tries to save the blogpost, but the database says "NOPE! I failed a uniqueness constraint"
Result: only one BlogPost with the name.
Now - to what you asked... and what I'm assuming is the mistaken understanding... and index is not an id.
Each record does not get the same index.
No record gets an index.
You can kinda pretend that an index is a lookup-table of all the values that are already set for this column.
What happens with a non-unique index is that you have a list of all the values... and the list of which records have that value. eg:
Widgets:
colour:
blue ids: 1,3,7
green ids: 2,4
red ids: 5,6
(totally made up example, nothing like reality)
When the index has a uniqueness constraint, it just has the same list, but only allows the db to store one id per value and if you try to store another one... it raises an exception
Race Condition in Ruby on Rails
after_create
is processed within the database transaction saving the text message. Therefore the callback that hits another controller cannot read the text message. It is not a good idea to have an external call within a database transaction, because the transaction blocks parts of the database for the whole time the slow external request takes.
The simples solution is to replace after_save
with after_commit
(see: http://apidock.com/rails/ActiveRecord/Transactions/ClassMethods/after_commit)
Since callbacks tend to become hard to understand (and may lead to problems when testing), I would prefer to make the call explicit by calling another method. Perhaps something like this:
# use instead of .save
def save_and_sent_sms
save and sent_sms
end
Perhaps you want to sent the sms in the background, so it does not slow down the web request for the user. Search for the gems delayed_job or resque for more information.
How do I avoid a simple race condition in Rails?
In order to achieve this you have to use some kind of locking. Basically you have 3 options: optimistic/pessimistic rails locking and some external locking backend (like Redis::Lock).
I personally would go for pessimistic locking if high performance is not really the case here
photo = Photo.find(photo_id)
photo.with_lock do
photo.num_votes += 1
photo.save!
end
I should also point out that sticking to only wrapping incrementing num_votes and save into one transaction would not solve the race-condition. Most RDBMS by default work in read committed mode. Which doesn't prevent such a race condition.
FYI See Pessimistic and Optimistic Locking reference
Related Topics
How to Do String Comparison in Ruby
Which Equality Test Does Ruby's Hash Use When Comparing Keys
How to Access the Current Node from a Library in a Chef Cookbook
How to Get Long Filename from Argv
How to Upload a File with Watir and Ie
Execjs and Could Not Find a JavaScript Runtime
Add a CSS Class to <%= F.Submit %>
Adding a Background Image in Ruby on Rails 2 in CSS
Rails 3.1 Load CSS in Particular Order
Rails 3.1 - Changing Default Scaffold Views and Template
Sass Variables Not Parsing Correctly - Undefined Variable: "$Ct-White"
How to Use Reference Images in SASS When Using Rails 3.1
Best Way to Handle Dynamic CSS in a Rails App