transaction_retry 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d1df0fe99befc0a42b9592bb8754bcbc2292eca74213bfad5d114e6840215e70
|
4
|
+
data.tar.gz: 4222a52c9ec709bdf073407ed5d68d90d79d9fcbee91b8830568b262c0210533
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d0cc9e6240e23876803a76f25f073af9870fa62ea32729a994eefb4895a03fe40a85036c1202a1f6da4a780491a487837d0d60df25ed6473ba88351e723ddfac
|
7
|
+
data.tar.gz: f832595233174a12720025a595e94546ee61c9b4a15836d72f9183fb41f86635c683614329831efb7895b583521ddf49203e2f34027a4ce26fa07d93451cfaa8
|
data/README.md
CHANGED
@@ -24,6 +24,24 @@ If you have a standalone ActiveRecord-based project you'll need to call:
|
|
24
24
|
|
25
25
|
__after__ connecting to the database.
|
26
26
|
|
27
|
+
## Database deadlock and serialization errors that are retried
|
28
|
+
|
29
|
+
#### MySQL
|
30
|
+
|
31
|
+
* Deadlock found when trying to get lock
|
32
|
+
* Lock wait timeout exceeded
|
33
|
+
|
34
|
+
#### PostgreSQL
|
35
|
+
|
36
|
+
* deadlock detected
|
37
|
+
* could not serialize access
|
38
|
+
|
39
|
+
#### SQLite
|
40
|
+
|
41
|
+
* The database file is locked
|
42
|
+
* A table in the database is locked
|
43
|
+
* Database lock protocol error
|
44
|
+
|
27
45
|
## Configuration
|
28
46
|
|
29
47
|
You can optionally configure transaction_retry gem in your config/initializers/transaction_retry.rb (or anywhere else):
|
data/lib/transaction_retry.rb
CHANGED
@@ -19,15 +19,32 @@ module TransactionRetry
|
|
19
19
|
def transaction_with_retry(*objects, &block)
|
20
20
|
retry_count = 0
|
21
21
|
|
22
|
+
opts = if objects.last.is_a? Hash
|
23
|
+
objects.last
|
24
|
+
else
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
retry_on = opts.delete(:retry_on)
|
29
|
+
max_retries = opts.delete(:max_retries) || TransactionRetry.max_retries
|
30
|
+
|
22
31
|
begin
|
23
32
|
transaction_without_retry(*objects, &block)
|
24
|
-
rescue ::ActiveRecord::TransactionIsolationConflict
|
25
|
-
raise if retry_count >=
|
33
|
+
rescue *[::ActiveRecord::TransactionIsolationConflict, *retry_on]
|
34
|
+
raise if retry_count >= max_retries
|
26
35
|
raise if tr_in_nested_transaction?
|
27
36
|
|
28
37
|
retry_count += 1
|
29
38
|
postfix = { 1 => 'st', 2 => 'nd', 3 => 'rd' }[retry_count] || 'th'
|
30
|
-
|
39
|
+
|
40
|
+
type_s = case $!
|
41
|
+
when ::ActiveRecord::TransactionIsolationConflict
|
42
|
+
"Transaction isolation conflict"
|
43
|
+
else
|
44
|
+
$!.class.name
|
45
|
+
end
|
46
|
+
|
47
|
+
logger.warn "#{type_s} detected. Retrying for the #{retry_count}-#{postfix} time..." if logger
|
31
48
|
tr_exponential_pause( retry_count )
|
32
49
|
retry
|
33
50
|
end
|
@@ -40,6 +57,13 @@ module TransactionRetry
|
|
40
57
|
# An ugly tr_ prefix is used to minimize the risk of method clash in the future.
|
41
58
|
def tr_exponential_pause( count )
|
42
59
|
seconds = TransactionRetry.wait_times[count-1] || 32
|
60
|
+
|
61
|
+
if TransactionRetry.fuzz
|
62
|
+
fuzz_factor = [seconds * 0.25, 1].max
|
63
|
+
|
64
|
+
seconds += rand * (fuzz_factor * 2) - fuzz_factor
|
65
|
+
end
|
66
|
+
|
43
67
|
sleep( seconds ) if seconds > 0
|
44
68
|
end
|
45
69
|
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'test_helper'
|
4
4
|
|
5
5
|
class TransactionWithRetryTest < MiniTest::Unit::TestCase
|
6
|
+
class CustomError < StandardError
|
7
|
+
end
|
6
8
|
|
7
9
|
def setup
|
8
10
|
@original_max_retries = TransactionRetry.max_retries
|
@@ -48,6 +50,38 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
|
|
48
50
|
QueuedJob.first.destroy
|
49
51
|
end
|
50
52
|
|
53
|
+
def test_does_not_retry_on_unknown_error
|
54
|
+
first_run = true
|
55
|
+
|
56
|
+
assert_raises( CustomError ) do
|
57
|
+
ActiveRecord::Base.transaction do
|
58
|
+
if first_run
|
59
|
+
first_run = false
|
60
|
+
message = "Deadlock found when trying to get lock"
|
61
|
+
raise CustomError, "random error"
|
62
|
+
end
|
63
|
+
QueuedJob.create!( :job => 'is cool!' )
|
64
|
+
end
|
65
|
+
end
|
66
|
+
assert_equal( 0, QueuedJob.count )
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_retries_on_custom_error
|
70
|
+
first_run = true
|
71
|
+
|
72
|
+
ActiveRecord::Base.transaction(retry_on: CustomError) do
|
73
|
+
if first_run
|
74
|
+
first_run = false
|
75
|
+
message = "Deadlock found when trying to get lock"
|
76
|
+
raise CustomError, "random error"
|
77
|
+
end
|
78
|
+
QueuedJob.create!( :job => 'is cool!' )
|
79
|
+
end
|
80
|
+
assert_equal( 1, QueuedJob.count )
|
81
|
+
|
82
|
+
QueuedJob.first.destroy
|
83
|
+
end
|
84
|
+
|
51
85
|
def test_does_not_retry_transaction_more_than_max_retries_times
|
52
86
|
TransactionRetry.max_retries = 1
|
53
87
|
run = 0
|
@@ -61,6 +95,20 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
|
|
61
95
|
end
|
62
96
|
|
63
97
|
assert_equal( 2, run ) # normal run + one retry
|
98
|
+
|
99
|
+
TransactionRetry.max_retries = 3
|
100
|
+
|
101
|
+
run = 0
|
102
|
+
|
103
|
+
assert_raises( ActiveRecord::TransactionIsolationConflict ) do
|
104
|
+
ActiveRecord::Base.transaction(max_retries: 1) do
|
105
|
+
run += 1
|
106
|
+
message = "Deadlock found when trying to get lock"
|
107
|
+
raise ActiveRecord::TransactionIsolationConflict.new( ActiveRecord::StatementInvalid.new( message ), message )
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
assert_equal( 2, run ) # normal run + one retry
|
64
112
|
end
|
65
113
|
|
66
114
|
def test_does_not_retry_nested_transaction
|
metadata
CHANGED
@@ -1,38 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transaction_retry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Piotr 'Qertoip' Włodarek
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2018-06-18 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: activerecord
|
16
|
-
requirement:
|
17
|
-
none: false
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: 3.0.11
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
|
-
version_requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.11
|
25
27
|
- !ruby/object:Gem::Dependency
|
26
28
|
name: transaction_isolation
|
27
|
-
requirement:
|
28
|
-
none: false
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
29
30
|
requirements:
|
30
|
-
- -
|
31
|
+
- - ">="
|
31
32
|
- !ruby/object:Gem::Version
|
32
33
|
version: 1.0.2
|
33
34
|
type: :runtime
|
34
35
|
prerelease: false
|
35
|
-
version_requirements:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.2
|
36
41
|
description: Retries database transaction on deadlock and transaction serialization
|
37
42
|
errors. Supports MySQL, PostgreSQL and SQLite (as long as you are using new drivers
|
38
43
|
mysql2, pg, sqlite3).
|
@@ -42,7 +47,7 @@ executables: []
|
|
42
47
|
extensions: []
|
43
48
|
extra_rdoc_files: []
|
44
49
|
files:
|
45
|
-
- .gitignore
|
50
|
+
- ".gitignore"
|
46
51
|
- Gemfile
|
47
52
|
- LICENSE
|
48
53
|
- README.md
|
@@ -65,27 +70,36 @@ files:
|
|
65
70
|
- transaction_retry.gemspec
|
66
71
|
homepage: https://github.com/qertoip/transaction_retry
|
67
72
|
licenses: []
|
73
|
+
metadata: {}
|
68
74
|
post_install_message:
|
69
75
|
rdoc_options: []
|
70
76
|
require_paths:
|
71
77
|
- lib
|
72
78
|
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
79
|
requirements:
|
75
|
-
- -
|
80
|
+
- - ">="
|
76
81
|
- !ruby/object:Gem::Version
|
77
82
|
version: 1.9.2
|
78
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
-
none: false
|
80
84
|
requirements:
|
81
|
-
- -
|
85
|
+
- - ">="
|
82
86
|
- !ruby/object:Gem::Version
|
83
87
|
version: '0'
|
84
88
|
requirements: []
|
85
89
|
rubyforge_project:
|
86
|
-
rubygems_version:
|
90
|
+
rubygems_version: 2.7.6
|
87
91
|
signing_key:
|
88
|
-
specification_version:
|
92
|
+
specification_version: 4
|
89
93
|
summary: Retries database transaction on deadlock and transaction serialization errors.
|
90
94
|
Supports MySQL, PostgreSQL and SQLite.
|
91
|
-
test_files:
|
95
|
+
test_files:
|
96
|
+
- test/db/all.rb
|
97
|
+
- test/db/db.rb
|
98
|
+
- test/db/migrations.rb
|
99
|
+
- test/db/queued_job.rb
|
100
|
+
- test/integration/active_record/base/transaction_with_retry_test.rb
|
101
|
+
- test/library_setup.rb
|
102
|
+
- test/log/.gitkeep
|
103
|
+
- test/test_console.rb
|
104
|
+
- test/test_helper.rb
|
105
|
+
- test/test_runner.rb
|