transaction_retry 1.0.2 → 1.0.3
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
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
|