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.
@@ -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):
@@ -38,4 +38,12 @@ module TransactionRetry
38
38
  @@wait_times = array_of_seconds
39
39
  end
40
40
 
41
+ def self.fuzz
42
+ @@fuzz ||= true
43
+ end
44
+
45
+ def self.fuzz=( val )
46
+ @@fuzz = val
47
+ end
48
+
41
49
  end
@@ -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 >= TransactionRetry.max_retries
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
- logger.warn "Transaction isolation conflict detected. Retrying for the #{retry_count}-#{postfix} time..." if logger
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
 
@@ -1,3 +1,3 @@
1
1
  module TransactionRetry
2
- VERSION = "1.0.2"
2
+ VERSION = "1.0.3"
3
3
  end
@@ -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.2
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: 2012-02-08 00:00:00.000000000 Z
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: &9671100 !ruby/object:Gem::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: *9671100
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: &9688920 !ruby/object:Gem::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: *9688920
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: 1.8.15
90
+ rubygems_version: 2.7.6
87
91
  signing_key:
88
- specification_version: 3
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