mperham/deadlock_retry — Issue #10: deadlock retry fails to save record that raises a deadlock in after hooks
Author: mdimas
Created: 2012-02-04 18:40:31+00:00
State: open

If a deadlock occurs in an after commit hook for a record deadlock_retry will retry the commit but issues an "UPDATE" instead of an "INSERT" statement, resulting in the record never being saved even though calling #save returns true. This is easily demonstrated with the following code:

require 'deadlock_retry'

class MyTest < ActiveRecord::Base

after_create :deadlock_error
after_commit :puts_after_commit, :on => :create

def deadlock_error
unless @errored
@errored = true
raise ActiveRecord::StatementInvalid.new("Deadlock found when trying to get lock")
end
return
end

def puts_after_commit
puts "After Commit"
end
end

my_test = MyTest.new(:name => "Testing")

if my_test.save
MyTest.find(my_test.id)
end

This will have the following output:

> my_test = MyTest.new(:name => "Testing")
=> #
> if my_test.save
?> MyTest.find(my_test.id)
?> end

After Commit
ActiveRecord::RecordNotFound: Couldn't find MyTest with ID=0
from /Users/dimas/.rvm/gems/ree-1.8.7-2011.03/gems/activerecord-3.0.11/lib/active_record/relation/finder_methods.rb:306:in `find_one'
from /Users/dimas/.rvm/gems/ree-1.8.7-2011.03/gems/activerecord-3.0.11/lib/active_record/relation/finder_methods.rb:291:in `find_with_ids'
from /Users/dimas/.rvm/gems/ree-1.8.7-2011.03/gems/activerecord-3.0.11/lib/active_record/relation/finder_methods.rb:107:in `find'
from /Users/dimas/.rvm/gems/ree-1.8.7-2011.03/gems/activerecord-3.0.11/lib/active_record/base.rb:444:in `__send__'
from /Users/dimas/.rvm/gems/ree-1.8.7-2011.03/gems/activerecord-3.0.11/lib/active_record/base.rb:444:in `find'
from (irb):22

And if you look at the log you'll see:

SQL (0.1ms) BEGIN
SQL (1.9ms) describe `my_tests`
AREL (0.2ms) INSERT INTO `my_tests` (`name`) VALUES ('Testing')
SQL (0.5ms) ROLLBACK
Deadlock detected on retry 1, restarting transaction
SQL (3.3ms) BEGIN
AREL (0.3ms) UPDATE `my_tests` SET `name` = 'Testing' WHERE `my_tests`.`id` = 0
SQL (0.1ms) COMMIT
MyTest Load (0.2ms) SELECT `my_tests`.* FROM `my_tests` WHERE `my_tests`.`id` = 0 LIMIT 1