transaction_retry_continued 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f41000c855c9718808af7de74dc4acc436d76618af8b026d38b797713e7de025
4
- data.tar.gz: 51ee0b37da0f5cf18af574ed42e7214f54a1198200b562bd2bb97f7a5af2cb5c
3
+ metadata.gz: 9593b4c28a4caca5aefedbd6543e7407b194216469bbab86f82e346afea00af1
4
+ data.tar.gz: 61ce1f23b2dbbc67af572171fcb5c369e9133d3aca95987d6148d333ee34bd76
5
5
  SHA512:
6
- metadata.gz: 82d5871454e5541cf21988354f6e7dbe0a70c40bc789f61e286998617ba7bfffdebd6b52d5fab53a5d175d497d3b4148664e86d68462cc3f76bfcd584c31dc29
7
- data.tar.gz: eee89ab0eaa1374b3e9d0d85554c4c8b702cfdd1fae55c6363ea6149f5150f8506f0ac059d849b53fc53c00c6fc5139bfba5c4bfaff05f33366d0e7d3069382a
6
+ metadata.gz: a502cf67b25e82cda4ab5aa3f1630a81b7e8effaf04cbbce10d1f94be7cba8768640c5d8ccd0da855909dc8156298b301cbfc802209bd69b4d33bc79ca8b7890
7
+ data.tar.gz: 817ecdee03df757c9f001e43fd68ef5943bad51435411234747ee4a8fe95a74b3a63b58d86094b099c69f5726ff8158654db6ec81687c814eb4dccf7839df23d
@@ -108,7 +108,7 @@ jobs:
108
108
  POSTGRESQL_DB_PASS: database
109
109
  POSTGRESQL_DB_NAME: transaction_retry_continued_test
110
110
  steps:
111
- - uses: actions/checkout@v2
111
+ - uses: actions/checkout@v4
112
112
  - name: Set up Ruby
113
113
  uses: ruby/setup-ruby@v1
114
114
  with:
@@ -116,6 +116,12 @@ jobs:
116
116
  bundler-cache: true
117
117
  - name: Run bundle update
118
118
  run: bundle update
119
+ - name: Setup Code Climate test-reporter
120
+ if: github.ref_name == 'master'
121
+ run: |
122
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
123
+ chmod +x ./cc-test-reporter
124
+ ./cc-test-reporter before-build
119
125
  - name: Start Mysql
120
126
  if: matrix.db == 'mysql2'
121
127
  run: |
@@ -158,20 +164,34 @@ jobs:
158
164
  - name: Shutdown database
159
165
  if: always() && (matrix.db == 'postgresql' || matrix.db == 'mysql2')
160
166
  run: docker stop database
161
- # - name: Coveralls Parallel
162
- # if: "${{ !env.ACT }}"
163
- # uses: coverallsapp/github-action@master
164
- # with:
165
- # github-token: "${{ secrets.GITHUB_TOKEN }}"
166
- # flag-name: run-${{ matrix.ruby }}-${{ matrix.activerecord }}-${{ matrix.db }}-${{ matrix.dbversion }}
167
- # parallel: true
168
- # finish:
169
- # needs: test
170
- # runs-on: ubuntu-latest
171
- # steps:
172
- # - name: Coveralls Finished
173
- # if: "${{ !env.ACT }}"
174
- # uses: coverallsapp/github-action@master
175
- # with:
176
- # github-token: "${{ secrets.GITHUB_TOKEN }}"
177
- # parallel-finished: true
167
+ - name: Format coverage
168
+ if: github.ref_name == 'master'
169
+ run: |
170
+ ./cc-test-reporter format-coverage -t simplecov -o coverage/cc_resultset.json
171
+ - name: Upload reports' artifacts
172
+ if: success() || failure()
173
+ uses: actions/upload-artifact@v4
174
+ with:
175
+ name: coverage_artifact_ruby_${{ matrix.ruby }}_ar_${{ matrix.activerecord }}_db_${{ matrix.db }}
176
+ if-no-files-found: ignore
177
+ path: coverage
178
+ retention-days: 1
179
+ finish:
180
+ needs: test
181
+ runs-on: ubuntu-latest
182
+ if: github.ref_name == 'master'
183
+ steps:
184
+ - uses: actions/checkout@v4
185
+ - name: Download Code Climate test-reporter
186
+ run: |
187
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
188
+ chmod +x ./cc-test-reporter
189
+ - name: Download reports' artifacts
190
+ uses: actions/download-artifact@v4
191
+ with:
192
+ path: coverage_reports
193
+ pattern: coverage_artifact_*
194
+ - name: sum-coverage
195
+ run: |
196
+ ./cc-test-reporter sum-coverage coverage_reports/coverage_artifact_ruby*/cc_resultset.json -o coverage/codeclimate.json
197
+ ./cc-test-reporter upload-coverage -r ${{secrets.CC_TEST_REPORTER_ID}} -i coverage/codeclimate.json
data/.gitignore CHANGED
@@ -4,3 +4,5 @@ Gemfile.lock
4
4
  pkg/*
5
5
  .idea
6
6
  test/log/*.log
7
+ coverage
8
+ gemfiles/*/Gemfile.*lock
data/Gemfile CHANGED
@@ -1,23 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "http://rubygems.org"
3
+ source 'http://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
- gemfile_local = File.expand_path '../Gemfile.local', __FILE__
7
+ gemfile_local = File.expand_path 'Gemfile.local', __dir__
8
8
  eval File.read(gemfile_local), binding, gemfile_local if File.exist? gemfile_local
9
- #
10
- # group :test do
11
- # gem 'transaction_isolation', git: 'https://github.com/alittlebit/transaction_isolation'
12
- # # Use the gem instead of a dated version bundled with Ruby
13
- # gem 'minitest', '5.3.4'
14
- # gem 'simplecov', :require => false
15
- # end
16
- #
17
- # group :development do
18
- # gem 'transaction_isolation', git: 'https://github.com/alittlebit/transaction_isolation'
19
- # gem 'rake'
20
- # # enhance irb
21
- # gem 'awesome_print', :require => false
22
- # gem 'pry', :require => false
23
- # end
9
+
10
+ gem 'rubocop', '~> 1.64'
data/README.md CHANGED
@@ -1,3 +1,8 @@
1
+ [![Gem Version](https://badge.fury.io/rb/transaction_retry_continued.svg)](https://badge.fury.io/rb/transaction_retry_continued)
2
+ [![Maintainability](https://api.codeclimate.com/v1/badges/2a527711b8077f4be2a2/maintainability)](https://codeclimate.com/github/iagopiimenta/transaction_retry_continued/maintainability)
3
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/2a527711b8077f4be2a2/test_coverage)](https://codeclimate.com/github/iagopiimenta/transaction_retry_continued/test_coverage)
4
+ [![CI PR Builds](https://github.com/iagopiimenta/transaction_retry_continued/actions/workflows/main.yml/badge.svg)](https://github.com/iagopiimenta/transaction_retry_continued/actions/workflows/main.yml)
5
+
1
6
  # Transaction Retry Continued
2
7
 
3
8
  > This is a community-driven continuation of the [`transaction_retry`](https://github.com/qertoip/transaction_retry) gem
@@ -31,7 +36,7 @@ __It works out of the box with Ruby on Rails__.
31
36
  If you have a standalone ActiveRecord-based project you'll need to call:
32
37
 
33
38
  ```ruby
34
- TransactionRetry.apply_activerecord_patch # after connecting to the database
39
+ TransactionRetry.apply_activerecord_patch # after connecting to the database
35
40
  ```
36
41
 
37
42
  ## Database deadlock and serialization errors that are retried
@@ -83,13 +88,23 @@ This gem was initially developed for and successfully works in production at [Ko
83
88
 
84
89
  Run tests on the selected database (mysql2 by default):
85
90
 
86
- db=mysql2 bundle exec rake test
87
- db=postgresql bundle exec rake test
88
- db=sqlite3 bundle exec rake test
91
+ ```bash
92
+ # passing desired database, active record version and ruby version
93
+ docker compose run -e db=sqlite3 -e BUNDLE_GEMFILE=gemfiles/activerecord-7.0/Gemfile.sqlite3 ruby_2_5 bash -c ./docker/test-ruby.sh
94
+
95
+ # db options: mysql2, postgresql, sqlite3
96
+ # active record version options: 5.2, 6.0, 6.1, 7.0
97
+ # ruby version options: 2.5, 2.7, 3.0, 3.1
98
+ ```
89
99
 
90
- Run tests on all supported databases:
100
+ Run tests on all supported databases by ruby version:
91
101
 
92
- ./tests
102
+ ```bash
103
+ docker compose up ruby_2_5
104
+ docker compose up ruby_2_7
105
+ docker compose up ruby_3_0
106
+ docker compose up ruby_3_1
107
+ ```
93
108
 
94
109
  Database configuration is hardcoded in test/db/db.rb; feel free to improve this and submit a pull request.
95
110
 
@@ -99,8 +114,8 @@ You should be very suspicious about any gem that monkey patches your stock Ruby
99
114
 
100
115
  This gem is carefully written to not be more intrusive than it needs to be:
101
116
 
102
- * wraps ActiveRecord::Base#transaction class method using alias_method to add new behaviour
103
- * introduces two new private class methods in ActiveRecord::Base (with names that should never collide)
117
+ - wraps ActiveRecord::Base#transaction class method using alias_method to add new behaviour
118
+ - introduces two new private class methods in ActiveRecord::Base (with names that should never collide)
104
119
 
105
120
  ## License
106
121
 
data/Rakefile CHANGED
@@ -1,11 +1,13 @@
1
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
2
4
 
3
5
  require 'rake/testtask'
4
6
 
5
7
  Rake::TestTask.new do |t|
6
- t.libs += ["test", "lib"]
8
+ t.libs += %w[test lib]
7
9
  t.pattern = 'test/integration/**/*_test.rb'
8
10
  t.verbose = true
9
11
  end
10
12
 
11
- task :default => [:test]
13
+ task default: [:test]
@@ -0,0 +1,6 @@
1
+ FROM ruby:2.5-slim
2
+
3
+ RUN apt-get update &&\
4
+ apt-get install -y build-essential git sqlite3 libsqlite3-dev mariadb-client libmariadb-dev postgresql-client libpq-dev &&\
5
+ apt-get clean &&\
6
+ rm -rf /var/lib/apt/lists/*
@@ -0,0 +1,6 @@
1
+ FROM ruby:2.7-slim
2
+
3
+ RUN apt-get update &&\
4
+ apt-get install -y build-essential git sqlite3 libsqlite3-dev mariadb-client libmariadb-dev postgresql-client libpq-dev &&\
5
+ apt-get clean &&\
6
+ rm -rf /var/lib/apt/lists/*
@@ -0,0 +1,6 @@
1
+ FROM ruby:3.0-slim
2
+
3
+ RUN apt-get update &&\
4
+ apt-get install -y build-essential git sqlite3 libsqlite3-dev mariadb-client libmariadb-dev postgresql-client libpq-dev &&\
5
+ apt-get clean &&\
6
+ rm -rf /var/lib/apt/lists/*
@@ -0,0 +1,6 @@
1
+ FROM ruby:3.0-slim
2
+
3
+ RUN apt-get update &&\
4
+ apt-get install -y build-essential git sqlite3 libsqlite3-dev mariadb-client libmariadb-dev postgresql-client libpq-dev &&\
5
+ apt-get clean &&\
6
+ rm -rf /var/lib/apt/lists/*
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # fix conflicts between different ruby versions
4
+ rm -rf gemfiles/**/Gemfile.*.lock
5
+
6
+ gemfiles=(
7
+ "gemfiles/activerecord-5.2/Gemfile.sqlite3"
8
+ "gemfiles/activerecord-6.0/Gemfile.sqlite3"
9
+ "gemfiles/activerecord-6.1/Gemfile.sqlite3"
10
+
11
+ "gemfiles/activerecord-5.2/Gemfile.mysql2"
12
+ "gemfiles/activerecord-6.0/Gemfile.mysql2"
13
+ "gemfiles/activerecord-6.1/Gemfile.mysql2"
14
+
15
+ "gemfiles/activerecord-5.2/Gemfile.postgresql"
16
+ "gemfiles/activerecord-6.0/Gemfile.postgresql"
17
+ "gemfiles/activerecord-6.1/Gemfile.postgresql"
18
+ )
19
+
20
+ for gemfile in "${gemfiles[@]}"; do
21
+ database=$(echo $gemfile | cut -d'.' -f3)
22
+
23
+ echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
24
+ BUNDLE_GEMFILE=$gemfile bundle install
25
+ BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
26
+ done
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # fix conflicts between different ruby versions
4
+ rm -rf gemfiles/**/Gemfile.*.lock
5
+
6
+ gemfiles=(
7
+ "gemfiles/activerecord-5.2/Gemfile.sqlite3"
8
+ "gemfiles/activerecord-6.0/Gemfile.sqlite3"
9
+ "gemfiles/activerecord-6.1/Gemfile.sqlite3"
10
+ "gemfiles/activerecord-7.0/Gemfile.sqlite3"
11
+
12
+ "gemfiles/activerecord-5.2/Gemfile.mysql2"
13
+ "gemfiles/activerecord-6.0/Gemfile.mysql2"
14
+ "gemfiles/activerecord-6.1/Gemfile.mysql2"
15
+ "gemfiles/activerecord-7.0/Gemfile.mysql2"
16
+
17
+ "gemfiles/activerecord-5.2/Gemfile.postgresql"
18
+ "gemfiles/activerecord-6.0/Gemfile.postgresql"
19
+ "gemfiles/activerecord-6.1/Gemfile.postgresql"
20
+ "gemfiles/activerecord-7.0/Gemfile.postgresql"
21
+ )
22
+
23
+ for gemfile in "${gemfiles[@]}"; do
24
+ database=$(echo $gemfile | cut -d'.' -f3)
25
+
26
+ echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
27
+ BUNDLE_GEMFILE=$gemfile bundle install
28
+ BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
29
+ done
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # fix conflicts between different ruby versions
4
+ rm -rf gemfiles/**/Gemfile.*.lock
5
+
6
+ gemfiles=(
7
+ "gemfiles/activerecord-6.0/Gemfile.sqlite3"
8
+ "gemfiles/activerecord-6.1/Gemfile.sqlite3"
9
+ "gemfiles/activerecord-7.0/Gemfile.sqlite3"
10
+
11
+ "gemfiles/activerecord-6.0/Gemfile.mysql2"
12
+ "gemfiles/activerecord-6.1/Gemfile.mysql2"
13
+ "gemfiles/activerecord-7.0/Gemfile.mysql2"
14
+
15
+ "gemfiles/activerecord-6.0/Gemfile.postgresql"
16
+ "gemfiles/activerecord-6.1/Gemfile.postgresql"
17
+ "gemfiles/activerecord-7.0/Gemfile.postgresql"
18
+ )
19
+
20
+ for gemfile in "${gemfiles[@]}"; do
21
+ database=$(echo $gemfile | cut -d'.' -f3)
22
+
23
+ echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
24
+ BUNDLE_GEMFILE=$gemfile bundle install
25
+ BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
26
+ done
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # fix conflicts between different ruby versions
4
+ rm -rf gemfiles/**/Gemfile.*.lock
5
+
6
+ gemfiles=(
7
+ "gemfiles/activerecord-6.0/Gemfile.sqlite3"
8
+ "gemfiles/activerecord-6.1/Gemfile.sqlite3"
9
+ "gemfiles/activerecord-7.0/Gemfile.sqlite3"
10
+
11
+ "gemfiles/activerecord-6.0/Gemfile.mysql2"
12
+ "gemfiles/activerecord-6.1/Gemfile.mysql2"
13
+ "gemfiles/activerecord-7.0/Gemfile.mysql2"
14
+
15
+ "gemfiles/activerecord-6.0/Gemfile.postgresql"
16
+ "gemfiles/activerecord-6.1/Gemfile.postgresql"
17
+ "gemfiles/activerecord-7.0/Gemfile.postgresql"
18
+ )
19
+
20
+ for gemfile in "${gemfiles[@]}"; do
21
+ database=$(echo $gemfile | cut -d'.' -f3)
22
+
23
+ echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
24
+ BUNDLE_GEMFILE=$gemfile bundle install
25
+ BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
26
+ done
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # fix conflicts between different ruby versions
4
+ rm -rf gemfiles/**/Gemfile.*.lock
5
+
6
+ echo BUNDLE_GEMFILE=$BUNDLE_GEMFILE db=$db bundle exec rake test
7
+ BUNDLE_GEMFILE=$BUNDLE_GEMFILE bundle install
8
+ BUNDLE_GEMFILE=$BUNDLE_GEMFILE db=$db bundle exec rake test
@@ -0,0 +1,60 @@
1
+ services:
2
+ ruby_2_5: &ruby_2_5
3
+ working_dir: /app
4
+ build:
5
+ context: .
6
+ dockerfile: docker/ruby-2.5/Dockerfile
7
+ volumes:
8
+ - .:/app
9
+ command: bash -c "bundle install && bundle exec rake test"
10
+ environment:
11
+ POSTGRESQL_DB_HOST: postgres
12
+ POSTGRESQL_DB_USER: transaction_retry_continued
13
+ POSTGRESQL_DB_PASS: database
14
+ POSTGRESQL_DB_NAME: transaction_isolation_continued_test
15
+ MYSQL_DB_HOST: mysql
16
+ MYSQL_DB_USER: root
17
+ MYSQL_DB_PASS: database
18
+ MYSQL_DB_NAME: transaction_isolation_continued_test
19
+ depends_on:
20
+ - mysql
21
+ - postgres
22
+ ruby_2_7:
23
+ <<: *ruby_2_5
24
+ build:
25
+ context: .
26
+ dockerfile: docker/ruby-2.7/Dockerfile
27
+ ruby_3_0:
28
+ <<: *ruby_2_5
29
+ command: ./docker/test-ruby-3.0.sh
30
+ build:
31
+ context: .
32
+ dockerfile: docker/ruby-3.0/Dockerfile
33
+ ruby_3_1:
34
+ <<: *ruby_2_5
35
+ command: ./docker/test-ruby-3.1.sh
36
+ build:
37
+ context: .
38
+ dockerfile: docker/ruby-3.1/Dockerfile
39
+ postgres:
40
+ image: postgres:9.6
41
+ environment:
42
+ POSTGRES_USER: transaction_retry_continued
43
+ POSTGRES_PASSWORD: database
44
+ POSTGRES_DB: transaction_isolation_continued_test
45
+ ports:
46
+ - "5432:5432"
47
+ healthcheck:
48
+ test: [ "CMD-SHELL", "pg_isready -q" ]
49
+ interval: 5s
50
+ timeout: 5s
51
+ retries: 5
52
+ mysql:
53
+ image: biarms/mysql:5.7
54
+ environment:
55
+ MYSQL_ROOT_PASSWORD: database
56
+ MYSQL_DATABASE: transaction_isolation_continued_test
57
+ ports:
58
+ - "3306:3306"
59
+ healthcheck:
60
+ test: "mysqladmin ping --host=0.0.0.0 --password=database --silent"
@@ -1,6 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec path: Dir.getwd
3
3
 
4
- gem 'transaction_isolation', git: 'https://github.com/iagopiimenta/transaction_isolation_continued', branch: 'setup-git-workflow'
4
+ gem 'transaction_isolation_continued'
5
5
 
6
- File.exist?(gemfile_local = File.expand_path('./Gemfile.local', Dir.getwd)) and eval File.read(gemfile_local), binding, gemfile_local
6
+ File.exist?(gemfile_local = File.expand_path('../Gemfile.local', __FILE__)) and eval File.read(gemfile_local), binding, gemfile_local
7
+
8
+ gem 'simplecov'
9
+ gem 'simplecov_json_formatter'
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
1
4
  require 'active_record/base'
2
5
 
3
6
  module TransactionRetry
4
7
  module ActiveRecord
5
8
  module Base
6
-
7
- def self.included( base )
8
- base.extend( ClassMethods )
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
9
11
  base.class_eval do
10
12
  class << self
11
13
  alias_method :transaction_without_retry, :transaction
@@ -13,70 +15,68 @@ module TransactionRetry
13
15
  end
14
16
  end
15
17
  end
16
-
18
+
17
19
  module ClassMethods
18
-
19
20
  def transaction_with_retry(**objects, &block)
20
21
  retry_count = 0
21
22
 
22
23
  opts = if objects.is_a? Hash
23
- objects
24
- else
25
- {}
26
- end
24
+ objects
25
+ else
26
+ {}
27
+ end
27
28
 
28
29
  retry_on = opts.delete(:retry_on)
29
30
  max_retries = opts.delete(:max_retries) || TransactionRetry.max_retries
30
31
 
31
32
  begin
32
33
  transaction_without_retry(**objects, &block)
33
- rescue *[::ActiveRecord::TransactionIsolationConflict, *retry_on]
34
+ rescue ::ActiveRecord::TransactionIsolationConflict, *retry_on
34
35
  raise if retry_count >= max_retries
35
36
  raise if tr_in_nested_transaction?
36
-
37
+
37
38
  retry_count += 1
38
39
  postfix = { 1 => 'st', 2 => 'nd', 3 => 'rd' }[retry_count] || 'th'
39
40
 
40
- type_s = case $!
41
- when ::ActiveRecord::TransactionIsolationConflict
42
- "Transaction isolation conflict"
43
- else
44
- $!.class.name
45
- end
41
+ type_s = case $ERROR_INFO
42
+ when ::ActiveRecord::TransactionIsolationConflict
43
+ 'Transaction isolation conflict'
44
+ else
45
+ $ERROR_INFO.class.name
46
+ end
46
47
 
47
- logger.warn "#{type_s} detected. Retrying for the #{retry_count}-#{postfix} time..." if logger
48
- tr_exponential_pause( retry_count )
48
+ logger&.warn "#{type_s} detected. Retrying for the #{retry_count}-#{postfix} time..."
49
+ tr_exponential_pause(retry_count)
49
50
  retry
50
51
  end
51
52
  end
52
-
53
- private
54
53
 
55
- # Sleep 0, 1, 2, 4, ... seconds up to the TransactionRetry.max_retries.
56
- # Cap the sleep time at 32 seconds.
57
- # An ugly tr_ prefix is used to minimize the risk of method clash in the future.
58
- def tr_exponential_pause( count )
59
- seconds = TransactionRetry.wait_times[count-1] || 32
54
+ private
60
55
 
61
- if TransactionRetry.fuzz
62
- fuzz_factor = [seconds * 0.25, 1].max
56
+ # Sleep 0, 1, 2, 4, ... seconds up to the TransactionRetry.max_retries.
57
+ # Cap the sleep time at 32 seconds.
58
+ # An ugly tr_ prefix is used to minimize the risk of method clash in the future.
59
+ def tr_exponential_pause(count)
60
+ seconds = TransactionRetry.wait_times[count - 1] || 32
63
61
 
64
- seconds += rand * (fuzz_factor * 2) - fuzz_factor
65
- end
62
+ if TransactionRetry.fuzz
63
+ fuzz_factor = [seconds * 0.25, 1].max
66
64
 
67
- sleep( seconds ) if seconds > 0
68
- end
69
-
70
- # Returns true if we are in the nested transaction (the one with :requires_new => true).
71
- # Returns false otherwise.
72
- # An ugly tr_ prefix is used to minimize the risk of method clash in the future.
73
- def tr_in_nested_transaction?
74
- connection.open_transactions != 0
65
+ seconds += rand * (fuzz_factor * 2) - fuzz_factor
75
66
  end
76
67
 
68
+ sleep(seconds) if seconds.positive?
69
+ end
70
+
71
+ # Returns true if we are in the nested transaction (the one with :requires_new => true).
72
+ # Returns false otherwise.
73
+ # An ugly tr_ prefix is used to minimize the risk of method clash in the future.
74
+ def tr_in_nested_transaction?
75
+ connection.open_transactions != 0
76
+ end
77
77
  end
78
78
  end
79
79
  end
80
80
  end
81
81
 
82
- ActiveRecord::Base.send( :include, TransactionRetry::ActiveRecord::Base )
82
+ ActiveRecord::Base.include TransactionRetry::ActiveRecord::Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TransactionRetry
2
- VERSION = "1.1.0"
4
+ VERSION = '1.2.0'
3
5
  end
@@ -1,10 +1,11 @@
1
- require "active_record"
2
- require "transaction_isolation"
1
+ # frozen_string_literal: true
3
2
 
4
- require_relative "transaction_retry/version"
3
+ require 'active_record'
4
+ require 'transaction_isolation'
5
5
 
6
- module TransactionRetry
6
+ require_relative 'transaction_retry/version'
7
7
 
8
+ module TransactionRetry
8
9
  # Must be called after ActiveRecord established a connection.
9
10
  # Only then we know which connection adapter is actually loaded and can be enhanced.
10
11
  # Please note ActiveRecord does not load unused adapters.
@@ -13,7 +14,7 @@ module TransactionRetry
13
14
  require_relative 'transaction_retry/active_record/base'
14
15
  end
15
16
 
16
- if defined?( ::Rails )
17
+ if defined?(::Rails)
17
18
  # Setup applying the patch after Rails is initialized.
18
19
  class Railtie < ::Rails::Railtie
19
20
  config.after_initialize do
@@ -26,7 +27,7 @@ module TransactionRetry
26
27
  @@max_retries ||= 3
27
28
  end
28
29
 
29
- def self.max_retries=( n )
30
+ def self.max_retries=(n)
30
31
  @@max_retries = n
31
32
  end
32
33
 
@@ -34,7 +35,7 @@ module TransactionRetry
34
35
  @@wait_times ||= [0, 1, 2, 4, 8, 16, 32]
35
36
  end
36
37
 
37
- def self.wait_times=( array_of_seconds )
38
+ def self.wait_times=(array_of_seconds)
38
39
  @@wait_times = array_of_seconds
39
40
  end
40
41
 
@@ -42,8 +43,7 @@ module TransactionRetry
42
43
  @@fuzz ||= true
43
44
  end
44
45
 
45
- def self.fuzz=( val )
46
+ def self.fuzz=(val)
46
47
  @@fuzz = val
47
48
  end
48
-
49
49
  end
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transaction_retry'
data/test/db/all.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record'
2
4
  require_relative 'db'
3
5
  require_relative 'migrations'
data/test/db/db.rb CHANGED
@@ -1,37 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
 
3
5
  module TransactionRetry
4
6
  module Test
5
7
  module Db
6
-
7
8
  def self.connect_to_mysql2
8
9
  ::ActiveRecord::Base.establish_connection(
9
- :adapter => "mysql2",
10
- :database => ENV['MYSQL_DB_NAME'],
11
- :host => ENV['MYSQL_DB_HOST'],
12
- :user => 'root',
13
- :password => ENV['MYSQL_DB_PASS']
10
+ adapter: 'mysql2',
11
+ database: ENV['MYSQL_DB_NAME'],
12
+ host: ENV['MYSQL_DB_HOST'],
13
+ user: 'root',
14
+ password: ENV['MYSQL_DB_PASS']
14
15
  )
15
16
  end
16
17
 
17
18
  def self.connect_to_postgresql
18
19
  ::ActiveRecord::Base.establish_connection(
19
- :adapter => "postgresql",
20
- :database => ENV['POSTGRESQL_DB_NAME'],
21
- :host => ENV['POSTGRESQL_DB_HOST'],
22
- :user => ENV['POSTGRESQL_DB_USER'],
23
- :password => ENV['POSTGRESQL_DB_PASS']
20
+ adapter: 'postgresql',
21
+ database: ENV['POSTGRESQL_DB_NAME'],
22
+ host: ENV['POSTGRESQL_DB_HOST'],
23
+ user: ENV['POSTGRESQL_DB_USER'],
24
+ password: ENV['POSTGRESQL_DB_PASS']
24
25
  )
25
26
  end
26
27
 
27
28
  def self.connect_to_sqlite3
28
29
  ActiveRecord::Base.establish_connection(
29
- :adapter => "sqlite3",
30
- :database => ":memory:",
31
- :verbosity => "silent"
30
+ adapter: 'sqlite3',
31
+ database: ':memory:',
32
+ verbosity: 'silent'
32
33
  )
33
34
  end
34
-
35
35
  end
36
36
  end
37
37
  end
@@ -1,20 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TransactionRetry
2
4
  module Test
3
5
  module Migrations
4
-
5
6
  def self.run!
6
7
  c = ::ActiveRecord::Base.connection
7
8
 
8
9
  # Queued Jobs
9
-
10
- c.create_table "queued_jobs", :force => true do |t|
11
- t.text "job", :null => false
12
- t.integer "status", :default => 0, :null => false
10
+
11
+ c.create_table 'queued_jobs', force: true do |t|
12
+ t.text 'job', null: false
13
+ t.integer 'status', default: 0, null: false
13
14
  t.timestamps
14
15
  end
15
-
16
16
  end
17
-
18
17
  end
19
18
  end
20
19
  end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class QueuedJob < ActiveRecord::Base
2
4
  end
@@ -1,4 +1,4 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'test_helper'
4
4
 
@@ -16,22 +16,22 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
16
16
  TransactionRetry.wait_times = @original_wait_times
17
17
  QueuedJob.delete_all
18
18
  end
19
-
19
+
20
20
  def test_does_not_break_transaction
21
21
  ActiveRecord::Base.transaction do
22
- QueuedJob.create!( :job => 'is fun!' )
23
- assert_equal( 1, QueuedJob.count )
22
+ QueuedJob.create!(job: 'is fun!')
23
+ assert_equal(1, QueuedJob.count)
24
24
  end
25
- assert_equal( 1, QueuedJob.count )
25
+ assert_equal(1, QueuedJob.count)
26
26
  QueuedJob.first.destroy
27
27
  end
28
28
 
29
29
  def test_does_not_break_transaction_rollback
30
30
  ActiveRecord::Base.transaction do
31
- QueuedJob.create!( :job => 'gives money!' )
31
+ QueuedJob.create!(job: 'gives money!')
32
32
  raise ActiveRecord::Rollback
33
33
  end
34
- assert_equal( 0, QueuedJob.count )
34
+ assert_equal(0, QueuedJob.count)
35
35
  end
36
36
 
37
37
  def test_retries_transaction_on_transaction_isolation_conflict
@@ -40,30 +40,29 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
40
40
  ActiveRecord::Base.transaction do
41
41
  if first_run
42
42
  first_run = false
43
- message = "Deadlock found when trying to get lock"
44
- raise ActiveRecord::TransactionIsolationConflict.new( message )
43
+ message = 'Deadlock found when trying to get lock'
44
+ raise ActiveRecord::TransactionIsolationConflict, message
45
45
  end
46
- QueuedJob.create!( :job => 'is cool!' )
46
+ QueuedJob.create!(job: 'is cool!')
47
47
  end
48
- assert_equal( 1, QueuedJob.count )
49
-
48
+ assert_equal(1, QueuedJob.count)
49
+
50
50
  QueuedJob.first.destroy
51
51
  end
52
52
 
53
53
  def test_does_not_retry_on_unknown_error
54
54
  first_run = true
55
55
 
56
- assert_raises( CustomError ) do
56
+ assert_raises(CustomError) do
57
57
  ActiveRecord::Base.transaction do
58
58
  if first_run
59
59
  first_run = false
60
- message = "Deadlock found when trying to get lock"
61
- raise CustomError, "random error"
60
+ raise CustomError, 'random error'
62
61
  end
63
- QueuedJob.create!( :job => 'is cool!' )
62
+ QueuedJob.create!(job: 'is cool!')
64
63
  end
65
64
  end
66
- assert_equal( 0, QueuedJob.count )
65
+ assert_equal(0, QueuedJob.count)
67
66
  end
68
67
 
69
68
  def test_retries_on_custom_error
@@ -72,13 +71,12 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
72
71
  ActiveRecord::Base.transaction(retry_on: CustomError) do
73
72
  if first_run
74
73
  first_run = false
75
- message = "Deadlock found when trying to get lock"
76
- raise CustomError, "random error"
74
+ raise CustomError, 'random error'
77
75
  end
78
- QueuedJob.create!( :job => 'is cool!' )
76
+ QueuedJob.create!(job: 'is cool!')
79
77
  end
80
- assert_equal( 1, QueuedJob.count )
81
-
78
+ assert_equal(1, QueuedJob.count)
79
+
82
80
  QueuedJob.first.destroy
83
81
  end
84
82
 
@@ -86,50 +84,47 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
86
84
  TransactionRetry.max_retries = 1
87
85
  run = 0
88
86
 
89
- assert_raises( ActiveRecord::TransactionIsolationConflict ) do
87
+ assert_raises(ActiveRecord::TransactionIsolationConflict) do
90
88
  ActiveRecord::Base.transaction do
91
89
  run += 1
92
- message = "Deadlock found when trying to get lock"
93
- raise ActiveRecord::TransactionIsolationConflict.new( message )
90
+ message = 'Deadlock found when trying to get lock'
91
+ raise ActiveRecord::TransactionIsolationConflict, message
94
92
  end
95
93
  end
96
-
97
- assert_equal( 2, run ) # normal run + one retry
94
+
95
+ assert_equal(2, run) # normal run + one retry
98
96
 
99
97
  TransactionRetry.max_retries = 3
100
98
 
101
99
  run = 0
102
100
 
103
- assert_raises( ActiveRecord::TransactionIsolationConflict ) do
101
+ assert_raises(ActiveRecord::TransactionIsolationConflict) do
104
102
  ActiveRecord::Base.transaction(max_retries: 1) do
105
103
  run += 1
106
- message = "Deadlock found when trying to get lock"
107
- raise ActiveRecord::TransactionIsolationConflict.new( message )
104
+ message = 'Deadlock found when trying to get lock'
105
+ raise ActiveRecord::TransactionIsolationConflict, message
108
106
  end
109
107
  end
110
-
111
- assert_equal( 2, run ) # normal run + one retry
108
+
109
+ assert_equal(2, run) # normal run + one retry
112
110
  end
113
111
 
114
112
  def test_does_not_retry_nested_transaction
115
113
  first_try = true
116
114
 
117
115
  ActiveRecord::Base.transaction do
118
-
119
- assert_raises( ActiveRecord::TransactionIsolationConflict ) do
120
- ActiveRecord::Base.transaction( :requires_new => true ) do
116
+ assert_raises(ActiveRecord::TransactionIsolationConflict) do
117
+ ActiveRecord::Base.transaction(requires_new: true) do
121
118
  if first_try
122
119
  first_try = false
123
- message = "Deadlock found when trying to get lock"
124
- raise ActiveRecord::TransactionIsolationConflict.new( message )
120
+ message = 'Deadlock found when trying to get lock'
121
+ raise ActiveRecord::TransactionIsolationConflict, message
125
122
  end
126
- QueuedJob.create!( :job => 'is cool!' )
123
+ QueuedJob.create!(job: 'is cool!')
127
124
  end
128
125
  end
129
-
130
126
  end
131
-
132
- assert_equal( 0, QueuedJob.count )
133
- end
134
127
 
128
+ assert_equal(0, QueuedJob.count)
129
+ end
135
130
  end
@@ -1,21 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Prepares application to be tested (requires files, connects to db, resets schema and data, applies patches, etc.)
2
4
 
3
5
  # Initialize database
4
6
  require 'db/all'
5
7
 
6
8
  case ENV['db']
7
- when 'mysql2'
8
- TransactionRetry::Test::Db.connect_to_mysql2
9
- when 'postgresql'
10
- TransactionRetry::Test::Db.connect_to_postgresql
11
- when 'sqlite3'
12
- TransactionRetry::Test::Db.connect_to_sqlite3
13
- else
14
- TransactionRetry::Test::Db.connect_to_mysql2
9
+ when 'mysql2'
10
+ TransactionRetry::Test::Db.connect_to_mysql2
11
+ when 'postgresql'
12
+ TransactionRetry::Test::Db.connect_to_postgresql
13
+ when 'sqlite3'
14
+ TransactionRetry::Test::Db.connect_to_sqlite3
15
+ else
16
+ TransactionRetry::Test::Db.connect_to_mysql2
15
17
  end
16
18
 
17
19
  require 'logger'
18
- ActiveRecord::Base.logger = Logger.new( File.expand_path( "#{File.dirname( __FILE__ )}/log/test.log" ) )
20
+ ActiveRecord::Base.logger = Logger.new(File.expand_path("#{File.dirname(__FILE__)}/log/test.log"))
19
21
 
20
22
  TransactionRetry::Test::Migrations.run!
21
23
 
data/test/test_helper.rb CHANGED
@@ -1,9 +1,12 @@
1
- # Load test coverage tool (must be loaded before any code)
2
- #require 'simplecov'
3
- #SimpleCov.start do
4
- # add_filter '/test/'
5
- # add_filter '/config/'
6
- #end
1
+ # frozen_string_literal: true
2
+
3
+ require 'simplecov'
4
+ require 'simplecov_json_formatter'
5
+ SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter
6
+
7
+ SimpleCov.start do
8
+ add_filter '/test/'
9
+ end
7
10
 
8
11
  # Load and initialize the application to be tested
9
12
  require 'library_setup'
data/test/test_runner.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  # Load all tests
4
- Dir.glob( "./**/*_test.rb" ).each { |test_file| require test_file }
6
+ Dir.glob('./**/*_test.rb').sort.each { |test_file| require test_file }
@@ -1,23 +1,24 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "transaction_retry/version"
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'transaction_retry/version'
4
5
 
5
6
  Gem::Specification.new do |s|
6
- s.name = "transaction_retry_continued"
7
+ s.name = 'transaction_retry_continued'
7
8
  s.version = TransactionRetry::VERSION
8
- s.authors = ["Iago Pimenta"]
9
- s.homepage = "https://github.com/iagopiimenta/transaction_retry_continued"
10
- s.summary = %q{Retries database transaction on deadlock and transaction serialization errors. Supports MySQL, PostgreSQL and SQLite.}
11
- s.description = %q{Retries database transaction on deadlock and transaction serialization errors. Supports MySQL, PostgreSQL and SQLite.}
9
+ s.authors = ['Iago Pimenta']
10
+ s.homepage = 'https://github.com/iagopiimenta/transaction_retry_continued'
11
+ s.summary = 'Retries database transaction on deadlock and transaction serialization errors. Supports MySQL, PostgreSQL and SQLite.'
12
+ s.description = 'Retries database transaction on deadlock and transaction serialization errors. Supports MySQL, PostgreSQL and SQLite.'
12
13
  s.required_ruby_version = '>= 2.5'
13
14
 
14
15
  s.files = `git ls-files`.split("\n")
15
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
- s.require_paths = ["lib"]
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
18
+ s.require_paths = ['lib']
18
19
 
19
- s.add_development_dependency 'rake', '~> 13.0'
20
20
  s.add_development_dependency 'minitest', '5.3.4'
21
- s.add_runtime_dependency "activerecord", ">= 5.2"
22
- s.add_runtime_dependency "transaction_isolation_continued", ">= 1.0"
23
- end
21
+ s.add_development_dependency 'rake', '~> 13.0'
22
+ s.add_runtime_dependency 'activerecord', '>= 5.2'
23
+ s.add_runtime_dependency 'transaction_isolation_continued', '>= 1.0'
24
+ end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transaction_retry_continued
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Iago Pimenta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-14 00:00:00.000000000 Z
11
+ date: 2024-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rake
14
+ name: minitest
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: '13.0'
19
+ version: 5.3.4
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: '13.0'
26
+ version: 5.3.4
27
27
  - !ruby/object:Gem::Dependency
28
- name: minitest
28
+ name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '='
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 5.3.4
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '='
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 5.3.4
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activerecord
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -79,7 +79,16 @@ files:
79
79
  - LICENSE
80
80
  - README.md
81
81
  - Rakefile
82
- - d
82
+ - docker-compose.yml
83
+ - docker/ruby-2.5/Dockerfile
84
+ - docker/ruby-2.7/Dockerfile
85
+ - docker/ruby-3.0/Dockerfile
86
+ - docker/ruby-3.1/Dockerfile
87
+ - docker/test-ruby-2.5.sh
88
+ - docker/test-ruby-2.7.sh
89
+ - docker/test-ruby-3.0.sh
90
+ - docker/test-ruby-3.1.sh
91
+ - docker/test-ruby.sh
83
92
  - gemfiles/Gemfile.base
84
93
  - gemfiles/activerecord-5.2/Gemfile.base
85
94
  - gemfiles/activerecord-5.2/Gemfile.mysql2
@@ -108,10 +117,8 @@ files:
108
117
  - test/integration/active_record/base/transaction_with_retry_test.rb
109
118
  - test/library_setup.rb
110
119
  - test/log/.gitkeep
111
- - test/test_console.rb
112
120
  - test/test_helper.rb
113
121
  - test/test_runner.rb
114
- - tests
115
122
  - transaction_retry_continued.gemspec
116
123
  homepage: https://github.com/iagopiimenta/transaction_retry_continued
117
124
  licenses: []
@@ -144,6 +151,5 @@ test_files:
144
151
  - test/integration/active_record/base/transaction_with_retry_test.rb
145
152
  - test/library_setup.rb
146
153
  - test/log/.gitkeep
147
- - test/test_console.rb
148
154
  - test/test_helper.rb
149
155
  - test/test_runner.rb
data/d DELETED
@@ -1 +0,0 @@
1
- bundle exec ruby test/test_console.rb
data/test/test_console.rb DELETED
@@ -1,11 +0,0 @@
1
- # Ensure that LOAD_PATH is the same as when running "rake test"; normally rake takes care of that
2
- $LOAD_PATH << File.expand_path( ".", File.dirname( __FILE__ ) )
3
- $LOAD_PATH << File.expand_path( "./lib", File.dirname( __FILE__ ) )
4
- $LOAD_PATH << File.expand_path( "./test", File.dirname( __FILE__ ) )
5
-
6
- # Boot the app
7
- require_relative 'library_setup'
8
-
9
- # Fire the console
10
- require 'pry'
11
- binding.pry
data/tests DELETED
@@ -1,6 +0,0 @@
1
-
2
- db=mysql2 bundle exec rake
3
-
4
- db=postgresql bundle exec rake
5
-
6
- db=sqlite3 bundle exec rake