openstreetmap-deadlock_retry 1.3.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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/CHANGELOG +26 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README +17 -0
- data/Rakefile +12 -0
- data/deadlock_retry.gemspec +19 -0
- data/lib/deadlock_retry.rb +95 -0
- data/lib/deadlock_retry/version.rb +3 -0
- data/test/deadlock_retry_test.rb +118 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ce513f5f2ec5497927c4048a901613319fec6e21
|
4
|
+
data.tar.gz: 7ccdfb8d58d590d25a4daf1a558a631cdccfa082
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0d2ad1ed7fb5e789bf5f0ebf49f65e1bb6483b170059ba10c2c09cf5364ba5053aff7968a7f86f11b679113e0f04e703f4f267566cbf9523a6cdfd7aa7da7c9d
|
7
|
+
data.tar.gz: d94bd56d0bd4ed59e2a9a236ff582ba2934be62a832357cb25c11163b62240b0f9c85e9bb56c2829d97b3f8bb17a23f1fab0161b3cf3f2af7d36185ddcc918b1
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.swp
|
data/CHANGELOG
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
deadlock_retry changes
|
2
|
+
|
3
|
+
== v1.2.0
|
4
|
+
|
5
|
+
* Support for postgres (tomhughes)
|
6
|
+
* Testing AR versions (kbrock)
|
7
|
+
|
8
|
+
== v1.1.2
|
9
|
+
|
10
|
+
* Exponential backoff, sleep 0, 1, 2, 4... seconds between retries.
|
11
|
+
* Support new syntax for InnoDB status in MySQL 5.5.
|
12
|
+
|
13
|
+
== v1.1.1 (2011-05-13)
|
14
|
+
|
15
|
+
* Conditionally log INNODB STATUS only if user has permission. (osheroff)
|
16
|
+
|
17
|
+
== v1.1.0 (2011-04-20)
|
18
|
+
|
19
|
+
* Modernize.
|
20
|
+
* Drop support for Rails 2.1 and earlier.
|
21
|
+
|
22
|
+
== v1.0 - (2009-02-07)
|
23
|
+
|
24
|
+
* Add INNODB status logging for debugging deadlock issues.
|
25
|
+
* Clean up so the code will run as a gem plugin.
|
26
|
+
* Small fix for ActiveRecord 2.1.x compatibility.
|
data/Gemfile
ADDED
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,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
|
5
|
+
require "deadlock_retry/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = %q{openstreetmap-deadlock_retry}
|
9
|
+
s.version = DeadlockRetry::VERSION
|
10
|
+
s.authors = ["Jamis Buck", "Mike Perham", "Tom Hughes"]
|
11
|
+
s.description = s.summary = %q{Provides automatic deadlock retry and logging functionality for ActiveRecord and MySQL}
|
12
|
+
s.email = %q{tom@compton.nu}
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.homepage = %q{http://github.com/mperham/deadlock_retry}
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.add_development_dependency 'mocha'
|
18
|
+
s.add_development_dependency 'activerecord', ENV['ACTIVERECORD_VERSION'] || ' ~>5.0'
|
19
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
|
3
|
+
module DeadlockRetry
|
4
|
+
mattr_accessor :innodb_status_cmd
|
5
|
+
|
6
|
+
DEADLOCK_ERROR_MESSAGES = [
|
7
|
+
"Deadlock found when trying to get lock",
|
8
|
+
"Lock wait timeout exceeded",
|
9
|
+
"deadlock detected"
|
10
|
+
]
|
11
|
+
|
12
|
+
MAXIMUM_RETRIES_ON_DEADLOCK = 3
|
13
|
+
|
14
|
+
|
15
|
+
def transaction(*objects, &block)
|
16
|
+
retry_count = 0
|
17
|
+
|
18
|
+
check_innodb_status_available
|
19
|
+
|
20
|
+
begin
|
21
|
+
super(*objects, &block)
|
22
|
+
rescue ActiveRecord::StatementInvalid => error
|
23
|
+
raise if in_nested_transaction?
|
24
|
+
if DEADLOCK_ERROR_MESSAGES.any? { |msg| error.message =~ /#{Regexp.escape(msg)}/ }
|
25
|
+
raise if retry_count >= MAXIMUM_RETRIES_ON_DEADLOCK
|
26
|
+
retry_count += 1
|
27
|
+
logger.info "Deadlock detected on retry #{retry_count}, restarting transaction"
|
28
|
+
log_innodb_status if DeadlockRetry.innodb_status_cmd
|
29
|
+
exponential_pause(retry_count)
|
30
|
+
retry
|
31
|
+
else
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
WAIT_TIMES = [0, 1, 2, 4, 8, 16, 32]
|
40
|
+
|
41
|
+
def exponential_pause(count)
|
42
|
+
sec = WAIT_TIMES[count-1] || 32
|
43
|
+
# sleep 0, 1, 2, 4, ... seconds up to the MAXIMUM_RETRIES.
|
44
|
+
# Cap the pause time at 32 seconds.
|
45
|
+
sleep(sec) if sec != 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def in_nested_transaction?
|
49
|
+
# open_transactions was added in 2.2's connection pooling changes.
|
50
|
+
connection.open_transactions != 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def show_innodb_status
|
54
|
+
self.connection.select_value(DeadlockRetry.innodb_status_cmd)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Should we try to log innodb status -- if we don't have permission to,
|
58
|
+
# we actually break in-flight transactions, silently (!)
|
59
|
+
def check_innodb_status_available
|
60
|
+
return unless DeadlockRetry.innodb_status_cmd == nil
|
61
|
+
|
62
|
+
if self.connection.adapter_name == "MySQL"
|
63
|
+
begin
|
64
|
+
mysql_version = self.connection.select_rows('show variables like \'version\'')[0][1]
|
65
|
+
cmd = if mysql_version < '5.5'
|
66
|
+
'show innodb status'
|
67
|
+
else
|
68
|
+
'show engine innodb status'
|
69
|
+
end
|
70
|
+
self.connection.select_value(cmd)
|
71
|
+
DeadlockRetry.innodb_status_cmd = cmd
|
72
|
+
rescue
|
73
|
+
logger.info "Cannot log innodb status: #{$!.message}"
|
74
|
+
DeadlockRetry.innodb_status_cmd = false
|
75
|
+
end
|
76
|
+
else
|
77
|
+
DeadlockRetry.innodb_status_cmd = false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def log_innodb_status
|
82
|
+
# show innodb status is the only way to get visiblity into why
|
83
|
+
# the transaction deadlocked. log it.
|
84
|
+
lines = show_innodb_status
|
85
|
+
logger.warn "INNODB Status follows:"
|
86
|
+
lines.each_line do |line|
|
87
|
+
logger.warn line
|
88
|
+
end
|
89
|
+
rescue => e
|
90
|
+
# Access denied, ignore
|
91
|
+
logger.info "Cannot log innodb status: #{e.message}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
ActiveRecord::Base.singleton_class.prepend(DeadlockRetry) if defined?(ActiveRecord)
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
# Change the version if you want to test a different version of ActiveRecord
|
4
|
+
gem 'activerecord', ENV['ACTIVERECORD_VERSION'] || ' ~>3.0'
|
5
|
+
require 'active_record'
|
6
|
+
require 'active_record/version'
|
7
|
+
puts "Testing ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
8
|
+
|
9
|
+
require 'test/unit'
|
10
|
+
require 'mocha'
|
11
|
+
require 'logger'
|
12
|
+
require "deadlock_retry"
|
13
|
+
|
14
|
+
class MockModel
|
15
|
+
@@open_transactions = 0
|
16
|
+
|
17
|
+
def self.transaction(*objects)
|
18
|
+
@@open_transactions += 1
|
19
|
+
yield
|
20
|
+
ensure
|
21
|
+
@@open_transactions -= 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.open_transactions
|
25
|
+
@@open_transactions
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.connection
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.logger
|
33
|
+
@logger ||= Logger.new(nil)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.show_innodb_status
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.select_rows(sql)
|
41
|
+
[['version', '5.1.45']]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.select_value(sql)
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.adapter_name
|
49
|
+
"MySQL"
|
50
|
+
end
|
51
|
+
|
52
|
+
include DeadlockRetry
|
53
|
+
end
|
54
|
+
|
55
|
+
class DeadlockRetryTest < Test::Unit::TestCase
|
56
|
+
DEADLOCK_ERROR = "MySQL::Error: Deadlock found when trying to get lock"
|
57
|
+
TIMEOUT_ERROR = "MySQL::Error: Lock wait timeout exceeded"
|
58
|
+
|
59
|
+
def setup
|
60
|
+
MockModel.stubs(:exponential_pause)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_no_errors
|
64
|
+
assert_equal :success, MockModel.transaction { :success }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_no_errors_with_deadlock
|
68
|
+
errors = [ DEADLOCK_ERROR ] * 3
|
69
|
+
assert_equal :success, MockModel.transaction { raise ActiveRecord::StatementInvalid, errors.shift unless errors.empty?; :success }
|
70
|
+
assert errors.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_no_errors_with_lock_timeout
|
74
|
+
errors = [ TIMEOUT_ERROR ] * 3
|
75
|
+
assert_equal :success, MockModel.transaction { raise ActiveRecord::StatementInvalid, errors.shift unless errors.empty?; :success }
|
76
|
+
assert errors.empty?
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_error_if_limit_exceeded
|
80
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
81
|
+
MockModel.transaction { raise ActiveRecord::StatementInvalid, DEADLOCK_ERROR }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_error_if_unrecognized_error
|
86
|
+
assert_raise(ActiveRecord::StatementInvalid) do
|
87
|
+
MockModel.transaction { raise ActiveRecord::StatementInvalid, "Something else" }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_included_by_default
|
92
|
+
assert ActiveRecord::Base.respond_to?(:transaction_with_deadlock_handling)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_innodb_status_availability
|
96
|
+
DeadlockRetry.innodb_status_cmd = nil
|
97
|
+
MockModel.transaction {}
|
98
|
+
assert_equal "show innodb status", DeadlockRetry.innodb_status_cmd
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def test_error_in_nested_transaction_should_retry_outermost_transaction
|
103
|
+
tries = 0
|
104
|
+
errors = 0
|
105
|
+
|
106
|
+
MockModel.transaction do
|
107
|
+
tries += 1
|
108
|
+
MockModel.transaction do
|
109
|
+
MockModel.transaction do
|
110
|
+
errors += 1
|
111
|
+
raise ActiveRecord::StatementInvalid, "MySQL::Error: Lock wait timeout exceeded" unless errors > 3
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
assert_equal 4, tries
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openstreetmap-deadlock_retry
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jamis Buck
|
8
|
+
- Mike Perham
|
9
|
+
- Tom Hughes
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2017-08-03 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mocha
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: activerecord
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - "~>"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '5.0'
|
36
|
+
type: :development
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '5.0'
|
43
|
+
description: Provides automatic deadlock retry and logging functionality for ActiveRecord
|
44
|
+
and MySQL
|
45
|
+
email: tom@compton.nu
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".gitignore"
|
51
|
+
- CHANGELOG
|
52
|
+
- Gemfile
|
53
|
+
- LICENSE
|
54
|
+
- README
|
55
|
+
- Rakefile
|
56
|
+
- deadlock_retry.gemspec
|
57
|
+
- lib/deadlock_retry.rb
|
58
|
+
- lib/deadlock_retry/version.rb
|
59
|
+
- test/deadlock_retry_test.rb
|
60
|
+
homepage: http://github.com/mperham/deadlock_retry
|
61
|
+
licenses: []
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.6.11
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: Provides automatic deadlock retry and logging functionality for ActiveRecord
|
83
|
+
and MySQL
|
84
|
+
test_files:
|
85
|
+
- test/deadlock_retry_test.rb
|