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.
@@ -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