deadlock_retry 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/CHANGELOG +12 -0
- data/LICENSE +20 -0
- data/README +17 -0
- data/Rakefile +12 -0
- data/deadlock_retry.gemspec +12 -0
- data/lib/deadlock_retry.rb +62 -0
- data/test/deadlock_retry_test.rb +90 -0
- metadata +64 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.swp
|
data/CHANGELOG
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
deadlock_retry changes
|
2
|
+
|
3
|
+
== v1.0.1
|
4
|
+
|
5
|
+
* Modernize.
|
6
|
+
* Drop support for Rails 2.1 and earlier.
|
7
|
+
|
8
|
+
== v1.0 - (2009-02-07)
|
9
|
+
|
10
|
+
* Add INNODB status logging for debugging deadlock issues.
|
11
|
+
* Clean up so the code will run as a gem plugin.
|
12
|
+
* Small fix for ActiveRecord 2.1.x compatibility.
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Copyright (c) 2005 Jamis Buck
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= Deadlock Retry
|
2
|
+
|
3
|
+
Deadlock retry allows the database adapter (currently only tested with the
|
4
|
+
MySQLAdapter) to retry transactions that fall into deadlock. It will retry
|
5
|
+
such transactions three times before finally failing.
|
6
|
+
|
7
|
+
This capability is automatically added to ActiveRecord. No code changes or otherwise are required.
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
Add it to your Rails application by installing the gem:
|
12
|
+
|
13
|
+
gem install deadlock_retry
|
14
|
+
|
15
|
+
and including a reference to it in your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'deadlock_retry'
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{deadlock_retry}
|
5
|
+
s.version = "1.1.0"
|
6
|
+
s.authors = ["Jamis Buck", "Mike Perham"]
|
7
|
+
s.description = s.summary = %q{Provides automatic deadlock retry and logging functionality for ActiveRecord and MySQL}
|
8
|
+
s.email = %q{mperham@gmail.com}
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.homepage = %q{http://github.com/mperham/deadlock_retry}
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module DeadlockRetry
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.class_eval do
|
6
|
+
class << self
|
7
|
+
alias_method_chain :transaction, :deadlock_handling
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
DEADLOCK_ERROR_MESSAGES = [
|
14
|
+
"Deadlock found when trying to get lock",
|
15
|
+
"Lock wait timeout exceeded"
|
16
|
+
]
|
17
|
+
|
18
|
+
MAXIMUM_RETRIES_ON_DEADLOCK = 3
|
19
|
+
|
20
|
+
def transaction_with_deadlock_handling(*objects, &block)
|
21
|
+
retry_count = 0
|
22
|
+
|
23
|
+
begin
|
24
|
+
transaction_without_deadlock_handling(*objects, &block)
|
25
|
+
rescue ActiveRecord::StatementInvalid => error
|
26
|
+
raise if in_nested_transaction?
|
27
|
+
if DEADLOCK_ERROR_MESSAGES.any? { |msg| error.message =~ /#{Regexp.escape(msg)}/ }
|
28
|
+
raise if retry_count >= MAXIMUM_RETRIES_ON_DEADLOCK
|
29
|
+
retry_count += 1
|
30
|
+
logger.info "Deadlock detected on retry #{retry_count}, restarting transaction"
|
31
|
+
log_innodb_status
|
32
|
+
retry
|
33
|
+
else
|
34
|
+
raise
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def in_nested_transaction?
|
42
|
+
# open_transactions was added in 2.2's connection pooling changes.
|
43
|
+
connection.open_transactions != 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def log_innodb_status
|
47
|
+
# show innodb status is the only way to get visiblity into why
|
48
|
+
# the transaction deadlocked. log it.
|
49
|
+
lines = connection.select_value("show innodb status")
|
50
|
+
logger.warn "INNODB Status follows:"
|
51
|
+
lines.each_line do |line|
|
52
|
+
logger.warn line
|
53
|
+
end
|
54
|
+
rescue Exception => e
|
55
|
+
# Access denied, ignore
|
56
|
+
logger.warn "Cannot log innodb status: #{e.message}"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
ActiveRecord::Base.send(:include, DeadlockRetry)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
# Change the version if you want to test a different version of ActiveRecord
|
4
|
+
gem 'activerecord', '3.0.7'
|
5
|
+
require 'active_record'
|
6
|
+
require 'active_record/version'
|
7
|
+
puts "Testing ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
8
|
+
|
9
|
+
require 'test/unit'
|
10
|
+
require 'logger'
|
11
|
+
require "deadlock_retry"
|
12
|
+
|
13
|
+
class MockModel
|
14
|
+
@@open_transactions = 0
|
15
|
+
|
16
|
+
def self.transaction(*objects)
|
17
|
+
@@open_transactions += 1
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
@@open_transactions -= 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.open_transactions
|
24
|
+
@@open_transactions
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.connection
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.logger
|
32
|
+
@logger ||= Logger.new(nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
include DeadlockRetry
|
36
|
+
end
|
37
|
+
|
38
|
+
class DeadlockRetryTest < Test::Unit::TestCase
|
39
|
+
DEADLOCK_ERROR = "MySQL::Error: Deadlock found when trying to get lock"
|
40
|
+
TIMEOUT_ERROR = "MySQL::Error: Lock wait timeout exceeded"
|
41
|
+
|
42
|
+
def test_no_errors
|
43
|
+
assert_equal :success, MockModel.transaction { :success }
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_no_errors_with_deadlock
|
47
|
+
errors = [ DEADLOCK_ERROR ] * 3
|
48
|
+
assert_equal :success, MockModel.transaction { raise ActiveRecord::StatementInvalid, errors.shift unless errors.empty?; :success }
|
49
|
+
assert errors.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_no_errors_with_lock_timeout
|
53
|
+
errors = [ TIMEOUT_ERROR ] * 3
|
54
|
+
assert_equal :success, MockModel.transaction { raise ActiveRecord::StatementInvalid, errors.shift unless errors.empty?; :success }
|
55
|
+
assert errors.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_error_if_limit_exceeded
|
59
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
60
|
+
MockModel.transaction { raise ActiveRecord::StatementInvalid, DEADLOCK_ERROR }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_error_if_unrecognized_error
|
65
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
66
|
+
MockModel.transaction { raise ActiveRecord::StatementInvalid, "Something else" }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_included_by_default
|
71
|
+
assert ActiveRecord::Base.respond_to?(:transaction_with_deadlock_handling)
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_error_in_nested_transaction_should_retry_outermost_transaction
|
75
|
+
tries = 0
|
76
|
+
errors = 0
|
77
|
+
|
78
|
+
MockModel.transaction do
|
79
|
+
tries += 1
|
80
|
+
MockModel.transaction do
|
81
|
+
MockModel.transaction do
|
82
|
+
errors += 1
|
83
|
+
raise ActiveRecord::StatementInvalid, "MySQL::Error: Lock wait timeout exceeded" unless errors > 3
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
assert_equal 4, tries
|
89
|
+
end
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deadlock_retry
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jamis Buck
|
9
|
+
- Mike Perham
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2011-04-20 00:00:00 -07:00
|
15
|
+
default_executable:
|
16
|
+
dependencies: []
|
17
|
+
|
18
|
+
description: Provides automatic deadlock retry and logging functionality for ActiveRecord and MySQL
|
19
|
+
email: mperham@gmail.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- .gitignore
|
28
|
+
- CHANGELOG
|
29
|
+
- LICENSE
|
30
|
+
- README
|
31
|
+
- Rakefile
|
32
|
+
- deadlock_retry.gemspec
|
33
|
+
- lib/deadlock_retry.rb
|
34
|
+
- test/deadlock_retry_test.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/mperham/deadlock_retry
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.6.2
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: Provides automatic deadlock retry and logging functionality for ActiveRecord and MySQL
|
63
|
+
test_files: []
|
64
|
+
|