Permalink
Browse files

check innodb status before the first transaction and record whether w…

…e can safely run it
  • Loading branch information...
1 parent 60523e8 commit 36ec3a500e727535c21485e70640835b62dd273e Ben Osheroff committed May 13, 2011
Showing with 44 additions and 32 deletions.
  1. +32 −8 lib/deadlock_retry.rb
  2. +12 −24 test/deadlock_retry_test.rb
View
@@ -1,10 +1,4 @@
module DeadlockRetry
-
- # Should we try to log innodb status -- if we don't have permission to,
- # we actually break in-flight transactions, silently (!)
- mattr_accessor :log_innodb_status
- self.log_innodb_status = false
-
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
@@ -14,6 +8,16 @@ class << self
end
end
+ @@innodb_status_available = nil
+
+ def self.innodb_status_available?
+ @@innodb_status_available
+ end
+
+ def self.innodb_status_available=(bool)
+ @@innodb_status_available = bool
+ end
+
module ClassMethods
DEADLOCK_ERROR_MESSAGES = [
"Deadlock found when trying to get lock",
@@ -22,9 +26,12 @@ module ClassMethods
MAXIMUM_RETRIES_ON_DEADLOCK = 3
+
def transaction_with_deadlock_handling(*objects, &block)
retry_count = 0
+ check_innodb_status_available
+
begin
transaction_without_deadlock_handling(*objects, &block)
rescue ActiveRecord::StatementInvalid => error
@@ -33,7 +40,7 @@ def transaction_with_deadlock_handling(*objects, &block)
raise if retry_count >= MAXIMUM_RETRIES_ON_DEADLOCK
retry_count += 1
logger.info "Deadlock detected on retry #{retry_count}, restarting transaction"
- log_innodb_status if DeadlockRetry.log_innodb_status
+ log_innodb_status if DeadlockRetry.innodb_status_available?
retry
else
raise
@@ -48,10 +55,27 @@ def in_nested_transaction?
connection.open_transactions != 0
end
+ def show_innodb_status
+ self.connection.select_value("show innodb status")
+ end
+
+ # Should we try to log innodb status -- if we don't have permission to,
+ # we actually break in-flight transactions, silently (!)
+ def check_innodb_status_available
+ return unless DeadlockRetry.innodb_status_available?.nil?
+
+ begin
+ show_innodb_status
+ DeadlockRetry.innodb_status_available = true
+ rescue
+ DeadlockRetry.innodb_status_available = false
+ end
+ end
+
def log_innodb_status
# show innodb status is the only way to get visiblity into why
# the transaction deadlocked. log it.
- lines = connection.select_value("show innodb status")
+ lines = show_innodb_status
logger.warn "INNODB Status follows:"
lines.each_line do |line|
logger.warn line
@@ -32,28 +32,22 @@ def self.logger
@logger ||= Logger.new(nil)
end
- include DeadlockRetry
-
- def self.log_innodb_status
- @logged = true
+ def self.show_innodb_status
+ []
end
- def self.was_logged
- @logged
+ def self.reset_innodb_status_availability
+ DeadlockRetry.innodb_status_availability = nil
end
- def self.clear_was_logged
- @logged = false
- end
+ include DeadlockRetry
end
class DeadlockRetryTest < Test::Unit::TestCase
DEADLOCK_ERROR = "MySQL::Error: Deadlock found when trying to get lock"
TIMEOUT_ERROR = "MySQL::Error: Lock wait timeout exceeded"
def setup
- DeadlockRetry.log_innodb_status = false
- MockModel.clear_was_logged
end
def test_no_errors
@@ -88,6 +82,13 @@ def test_included_by_default
assert ActiveRecord::Base.respond_to?(:transaction_with_deadlock_handling)
end
+ def test_innodb_status_availability
+ DeadlockRetry.innodb_status_available = nil
+ MockModel.transaction {}
+ assert_equal true, DeadlockRetry.innodb_status_available?
+ end
+
+
def test_error_in_nested_transaction_should_retry_outermost_transaction
tries = 0
errors = 0
@@ -104,17 +105,4 @@ def test_error_in_nested_transaction_should_retry_outermost_transaction
assert_equal 4, tries
end
-
- def test_should_not_log_innodb_by_default
- errors = [ DEADLOCK_ERROR ] * 3
- MockModel.transaction { raise ActiveRecord::StatementInvalid, errors.shift unless errors.empty?;}
- assert !MockModel.was_logged
- end
-
- def test_should_log_if_logging_enabled
- errors = [ DEADLOCK_ERROR ] * 3
- DeadlockRetry.log_innodb_status = true
- MockModel.transaction { raise ActiveRecord::StatementInvalid, errors.shift unless errors.empty?;}
- assert MockModel.was_logged
- end
end

0 comments on commit 36ec3a5

Please sign in to comment.