sidekiq-transaction_guard 1.0.0

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: 294e37857447f017a08970a3d0430042c69e0afee645d25ce3f2350ce93fb9e6
4
+ data.tar.gz: ee1c16813ca499c9c146fa3db6dcc4f7d3f1c4b8417769ca1fa9635cdcd793c0
5
+ SHA512:
6
+ metadata.gz: b533961d40cc75425032fbb39ecf51a216bca82cf50f3e5d45d2b6c0a628b7da38b03e4c1bc5387711d4a4ddb36a120626826156d42020fd768ff6adb0e16069
7
+ data.tar.gz: 520048075eb69133f17233a5d8ef037e8d8f6ce4c886be5c1f398b966154bb7c0e27ed1a6e4c4694e17d46abd1e69a0651b5f72cd44c13520a2b8d07bbb05907
@@ -0,0 +1,3 @@
1
+ # 1.0.0
2
+
3
+ * Initial release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2018 Brian Durand, Winston Durand
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,158 @@
1
+ # Sidekiq::TransactionGuard
2
+
3
+ [![Build Status](https://travis-ci.com/bdurand/sidekiq-transaction_guard.svg?branch=master)](https://travis-ci.com/bdurand/sidekiq-transaction_guard)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/17bbf5cb6eda022028fe/maintainability)](https://codeclimate.com/github/bdurand/sidekiq-transaction_guard/maintainability)
5
+
6
+ You should never call a Sidekiq worker that relies on the state of the database from within a database transaction. You will end up with a race condition since the worker could kick off before the transaction is actually written to the database. This gem can be used to highlight where your code may be scheduling workers in an indeterminate state.
7
+
8
+ ## The Problem
9
+
10
+ Consider this case:
11
+
12
+ ```ruby
13
+ class Post < ActiveRecord::Base
14
+ # BAD: DO NOT DO THIS
15
+ after_create do
16
+ PostCreatedWorker.perform_async(id)
17
+ end
18
+ end
19
+
20
+ class PostCreatedWorker
21
+ include Sidekiq::Worker
22
+
23
+ def perform(post_id)
24
+ post = Post.find_by(id: post_id)
25
+ if post
26
+ do_something_with(post)
27
+ end
28
+ end
29
+ end
30
+ ```
31
+
32
+ In this case, the `PostCreatedWorker` job will be created for a new `Post` record in Sidekiq before the data is actually written to the database. If Sidekiq picks up that worker and tries to execute it before the transaction is committed, `Post.find_by(id: post_id)` won't find anything and the worker will exit without performing it's task. Even if the worker doesn't need to read from the database, there is still a chance for an error to rollback the transaction leaving a possibility of workers running that should not have been scheduled.
33
+
34
+ To solve this, workers like this should be invoked in ActiveRecord from an `after_commit` callback. These callbacks are guaranteed to only execute after the data has been written to the database. However, as your application grows and gets more complicated, it can be difficult to ensure that workers are not being scheduled in the middle of transactions.
35
+
36
+ Switching from callbacks to service objects won't help you either, because service objects can be wrapped in transactions as well. The will just give you a new problem to solve.
37
+
38
+ ```ruby
39
+ class CreatePost
40
+ def initialize(attributes)
41
+ @attributes = attributes
42
+ end
43
+
44
+ def call
45
+ post = Post.create!(attributes)
46
+ PostCreatedWorker.perform_async(post.id)
47
+ end
48
+ end
49
+
50
+ # Still calling `perform_async` inside a transaction.
51
+ Post.transaction do
52
+ CreatePost.new(post_1_attributes)
53
+ CreatePost.new(post_2_attributes)
54
+ end
55
+ ```
56
+
57
+ ## The Solution
58
+
59
+ You can use this gem to add Sidekiq client middleware that will either warn you or raise an error when workers are scheduled inside of a database transaction. You can do this by simply adding this to your application's initialization code:
60
+
61
+ ```ruby
62
+ require 'sidekiq/transaction-guard'
63
+
64
+ Sidekiq.configure_client do |config|
65
+ config.client_middleware do |chain|
66
+ chain.add(Sidekiq::TransactionGuard::Middleware)
67
+ end
68
+ end
69
+ ```
70
+
71
+ ### Mode
72
+
73
+ By default, the behavior is to log that a worker is being scheduled inside of a transaction to the `Sidekiq.logger`. If you are running a test suite, you may want to expose the problematic calls by either raising errors or logging the calls to standard error. The mode can be one of `[:warn, :stderr, :error, :disabled]`.
74
+
75
+ ```ruby
76
+ # Raise errors
77
+ Sidekiq::TransactionGuard.mode = :error
78
+
79
+ # Log to STDERR
80
+ Sidekiq::TransactionGuard.mode = :stderr
81
+
82
+ # Log to Sidekiq.logger
83
+ Sidekiq::TransactionGuard.mode = :warn
84
+
85
+ # Disable entirely
86
+ Sidekiq::TransactionGuard.mode = :disabled
87
+ ```
88
+
89
+ You can also set the mode on individual worker classes with `sidekiq_options transaction_guard: mode`.
90
+
91
+ ```ruby
92
+ class SomeWorker
93
+ include Sidekiq::Worker
94
+
95
+ sidekiq_options transaction_guard: :error
96
+ end
97
+ ```
98
+
99
+
100
+ You can use the `:disabled` mode to allow individual worker classes to be scheduled inside of transactions where the worker logic doesn't care about the state of the database. For instance, if you use a Sidekiq worker to report errors, you would want to all it inside of transactions. If you don't control the worker you want to change the mode on, you simply call this in an initializer:
101
+
102
+ ```ruby
103
+ SomeWorker.sidekiq_options.merge(transaction_guard: :disabled)
104
+ ```
105
+
106
+ You could
107
+
108
+ ### Notification Handlers
109
+
110
+ You can also set a block to be called if a worker is scheduled inside of a transaction. This can be useful if you use an error logging service to notify you of problematic calls in production so you can fix them.
111
+
112
+ ```ruby
113
+ # Define a global notify handler
114
+ Sidekiq::TransactionGuard.notify do |job|
115
+ # Do what ever you need to. The job argument will be a Sidekiq job hash.
116
+ end
117
+
118
+ # Define on a per worker level
119
+ class SomeWorker
120
+ include Sidekiq::Worker
121
+
122
+ sidekiq_options notify_in_transaction: -> (job) { # Do something }
123
+ end
124
+
125
+ # Disable the global notification handler on a worker
126
+ class SomeOtherWorker
127
+ include Sidekiq::Worker
128
+
129
+ sidekiq_options notify_in_transaction: false
130
+ end
131
+ ```
132
+
133
+ ## Multiple Databases
134
+
135
+ Out of the box, this gem only deals with one database and monitors the connection pool returned by `ActiveRecord::Base.connection`. If you have multiple databases (or even multiple connections to the same database) that you want to track, you need to tell `Sidekiq::TransactionGuard` about them.
136
+
137
+ ```ruby
138
+ class MyClass < ActiveRecord::Base
139
+ # This estabilishes a new connection pool.
140
+ establish_connection(configurations["otherdb"])
141
+ end
142
+
143
+ Sidekiq::TransactionGuard.add_connection_class(MyClass)
144
+ ```
145
+
146
+ The class is used to get to the connection pool used for the class. You only need to add one class per connection pool, so you don't need to add any subclasses of `MyClass`.
147
+
148
+ ## Transaction Fixtures In Tests
149
+
150
+ If you're using transaction fixtures in your tests, there will always be a database transaction open. If you're using [DatabaseCleaner](https://github.com/DatabaseCleaner/database_cleaner) in your tests, you just need to include this snippet in your test suite initializer:
151
+
152
+ ```ruby
153
+ require 'sidekiq/transaction_guard/database_cleaner'
154
+ ```
155
+
156
+ This will add the appropriate code so that the surrounding transaction in the test suite is ignored (i.e. workers will only warn/error if there is more than one open transaction).
157
+
158
+ If you're using something else for your transactional fixtures or have some other weird setup, look in the `lib/sidekiq_transaction_guard/database_cleaner.rb` file for an example of what you need to do.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1 @@
1
+ require_relative "sidekiq/transaction_guard"
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sidekiq'
4
+ require 'set'
5
+ require 'thread'
6
+
7
+ require_relative 'transaction_guard/middleware'
8
+
9
+ module Sidekiq
10
+ module TransactionGuard
11
+ class InsideTransactionError < StandardError
12
+ end
13
+
14
+ @lock = Mutex.new
15
+ @connection_classes = Set.new
16
+ @notify = nil
17
+ @mode = :warn
18
+
19
+ class << self
20
+ VALID_MODES = [:warn, :stderr, :error, :disabled].freeze
21
+
22
+ # Set the global mode to one of `[:warn, :stderr, :error, :disabled]`. The
23
+ # default mode is `:warn`. This controls the behavior of workers enqueued
24
+ # inside of transactions.
25
+ # * :warn - Log to Sidekiq.logger
26
+ # * :stderr - Log to STDERR
27
+ # * :error - Throw a `Sidekiq::TransactionGuard::InsideTransactionError`
28
+ # * :disabled - Allow workers inside of transactions
29
+ def mode=(symbol)
30
+ if VALID_MODES.include?(symbol)
31
+ @mode = symbol
32
+ else
33
+ raise ArgumentError.new("mode must be one of #{VALID_MODES.inspect}")
34
+ end
35
+ end
36
+
37
+ # Return the current mode.
38
+ def mode
39
+ @mode
40
+ end
41
+
42
+ # Define the global notify block. This block will be called with a Sidekiq
43
+ # job hash for all jobs enqueued inside transactions if the mode is `:warn`
44
+ # or `:stderr`.
45
+ def notify(&block)
46
+ @notify = block
47
+ end
48
+
49
+ # Return the block set as the notify handler with a call to `notify`.
50
+ def notify_block
51
+ @notify
52
+ end
53
+
54
+ # Add a class that maintains it's own connection pool to the connections
55
+ # being monitored for open transactions. You don't need to add `ActiveRecord::Base`
56
+ # or subclasses. Only the base class that establishes a new connection pool
57
+ # with a call to `establish_connection` needs to be added.
58
+ def add_connection_class(connection_class)
59
+ @lock.synchronize{ @connection_classes << connection_class }
60
+ end
61
+
62
+ # Return true if any connection is currently inside of a transaction.
63
+ def in_transaction?
64
+ connection_classes = [ActiveRecord::Base]
65
+ unless @connection_classes.empty?
66
+ connection_classes.concat(@lock.synchronize{ @connection_classes.to_a })
67
+ end
68
+ connection_classes.any? do |connection_class|
69
+ connection_pool = connection_class.connection_pool
70
+ connection = connection_class.connection if connection_pool.active_connection?
71
+ if connection
72
+ connection.open_transactions > allowed_transaction_level(connection_class)
73
+ else
74
+ false
75
+ end
76
+ end
77
+ end
78
+
79
+ # This method call needs to be wrapped around tests that use transactional fixtures.
80
+ # It sets up data structures used to track the number of open transactions.
81
+ def testing(&block)
82
+ var = :sidekiq_rails_transaction_guard
83
+ save_val = Thread.current[var]
84
+ begin
85
+ Thread.current[var] = (save_val ? save_val.dup : {})
86
+ yield
87
+ ensure
88
+ Thread.current[var] = save_val
89
+ end
90
+ end
91
+
92
+ # This method needs to be called to set the allowed transaction level for a connection
93
+ # class (see `add_connection_class` for more info). The current transaction level
94
+ # for that class' connection will be set as the zero point. This method can only
95
+ # be called inside a block wrapped with the `testing` method.
96
+ def set_allowed_transaction_level(connection_class)
97
+ connection_counts = Thread.current[:sidekiq_rails_transaction_guard]
98
+ unless connection_counts
99
+ raise("set_allowed_transaction_level is only allowed inside a testing block")
100
+ end
101
+ connection_counts[connection_class.name] = connection_class.connection.open_transactions if connection_counts
102
+ end
103
+
104
+ private
105
+
106
+ def allowed_transaction_level(connection_class)
107
+ connection_counts = Thread.current[:sidekiq_rails_transaction_guard]
108
+ (connection_counts && connection_counts[connection_class.name]) || 0
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ # Configure the default transaction guard mode for known testing environments.
115
+ if ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
116
+ Sidekiq::TransactionGuard.mode = :stderr
117
+ end
@@ -0,0 +1,26 @@
1
+ require 'sidekiq/transaction_guard'
2
+ require 'database_cleaner'
3
+ require 'database_cleaner/active_record/transaction'
4
+
5
+ module Sidekiq
6
+ module TransactionGuard
7
+ module DatabaseCleaner
8
+ # Override the start method to set the base number of allowed transactions to
9
+ # the current level. Anything above this number will then be considered to be
10
+ # in a transaction.
11
+ def start
12
+ retval = super
13
+ Sidekiq::TransactionGuard.set_allowed_transaction_level(connection_class)
14
+ retval
15
+ end
16
+
17
+ # Wrap the `Sidekiq::TransactionGuard.testing` which sets up the data structures
18
+ # needed for custom counting of the transaction level within a test block.
19
+ def cleaning(&block)
20
+ Sidekiq::TransactionGuard.testing{ super(&block) }
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ ::DatabaseCleaner::ActiveRecord::Transaction.send(:prepend, Sidekiq::TransactionGuard::DatabaseCleaner)
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module TransactionGuard
5
+ # Sidekiq client middleware that will warn/error when workers are called inside of
6
+ # a database transaction.
7
+ #
8
+ # This middleware can read `sidekiq_options` set on the worker for
9
+ # `:transaction_guard` and `:notify_in_transaction` which will override
10
+ # the default behavior set in `Sidekiq::TransactionGuard.mode` and
11
+ # `Sidekiq::TransactionGuard.notify` respectively.
12
+ class Middleware
13
+ def call(worker_class, job, queue, redis_pool)
14
+ # Check if we need to log this. Also, convert worker_class to its actual class
15
+ log_transaction(worker_class.constantize, job) if in_transaction?
16
+
17
+ yield
18
+ end
19
+
20
+ private
21
+
22
+ def worker_mode(worker_class)
23
+ read_sidekiq_option(worker_class, :transaction_guard) || Sidekiq::TransactionGuard.mode
24
+ end
25
+
26
+ def in_transaction?
27
+ Sidekiq::TransactionGuard.in_transaction?
28
+ end
29
+
30
+ def notify_block(worker_class)
31
+ handler = read_sidekiq_option(worker_class, :notify_in_transaction)
32
+ if handler
33
+ handler
34
+ elsif handler == false
35
+ nil
36
+ else
37
+ Sidekiq::TransactionGuard.notify_block
38
+ end
39
+ end
40
+
41
+ def read_sidekiq_option(worker_class, option_name)
42
+ options = worker_class.sidekiq_options_hash
43
+ options[option_name.to_s] if options
44
+ end
45
+
46
+ def notify!(worker_class, job)
47
+ notify_handler = notify_block(worker_class)
48
+ if notify_handler
49
+ begin
50
+ notify_handler.call(job)
51
+ rescue => e
52
+ if Sidekiq.logger
53
+ Sidekiq.logger.error(e)
54
+ else
55
+ STDERR.write("ERROR on Sidekiq::TransactionGuard notify block for #{worker_class}: #{e.inspect}\n")
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def log_transaction(worker_class, job)
62
+ mode = worker_mode(worker_class)
63
+ if mode != :disabled
64
+ message = "#{worker_class.name} was called from inside a database transaction"
65
+ if mode == :error
66
+ raise Sidekiq::TransactionGuard::InsideTransactionError.new(message)
67
+ else
68
+ logger = Sidekiq.logger unless mode == :stderr
69
+ if logger
70
+ logger.warn(message)
71
+ else
72
+ STDERR.write("WARNING #{message}\n")
73
+ end
74
+ notify!(worker_class, job)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module TransactionGuard
5
+ VERSION = File.read(File.expand_path("../../../../VERSION", __FILE__)).chomp.freeze
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/sidekiq/transaction_guard/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "sidekiq-transaction_guard"
7
+ spec.version = Sidekiq::TransactionGuard::VERSION
8
+ spec.authors = ["Brian Durand", "Winston Durand"]
9
+ spec.email = ["bbdurand@gmail.com", "me@winstondurand.com"]
10
+
11
+ spec.summary = "Protect from accidentally invoking Sidekiq jobs when there are open database transactions"
12
+ spec.homepage = "https://github.com/bdurand/sidekiq-transaction_guard"
13
+ spec.license = "MIT"
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ ignore_files = %w(
18
+ .gitignore
19
+ .travis.yml
20
+ Appraisals
21
+ Gemfile
22
+ Gemfile.lock
23
+ Rakefile
24
+ gemfiles/
25
+ spec/
26
+ )
27
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
+ `git ls-files -z`.split("\x0").reject{ |f| ignore_files.any?{ |path| f.start_with?(path) } }
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ spec.required_ruby_version = '>= 2.2.2'
35
+
36
+ spec.add_dependency "activerecord", ">= 4.0"
37
+ spec.add_dependency "sidekiq", ">= 3.0"
38
+
39
+ spec.add_development_dependency "bundler", "~> 1.10"
40
+ spec.add_development_dependency "rake"
41
+ spec.add_development_dependency "rspec", "~> 3.0"
42
+ spec.add_development_dependency "database_cleaner"
43
+ spec.add_development_dependency "sqlite3"
44
+ spec.add_development_dependency "appraisal"
45
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq-transaction_guard
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Durand
8
+ - Winston Durand
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2018-10-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '4.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '4.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: sidekiq
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '3.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.10'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.10'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: database_cleaner
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: sqlite3
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: appraisal
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description:
127
+ email:
128
+ - bbdurand@gmail.com
129
+ - me@winstondurand.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - CHANGELOG.md
135
+ - MIT_LICENSE.txt
136
+ - README.md
137
+ - VERSION
138
+ - lib/sidekiq-transaction_guard.rb
139
+ - lib/sidekiq/transaction_guard.rb
140
+ - lib/sidekiq/transaction_guard/database_cleaner.rb
141
+ - lib/sidekiq/transaction_guard/middleware.rb
142
+ - lib/sidekiq/transaction_guard/version.rb
143
+ - sidekiq-transaction_guard.gemspec
144
+ homepage: https://github.com/bdurand/sidekiq-transaction_guard
145
+ licenses:
146
+ - MIT
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: 2.2.2
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.7.6
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Protect from accidentally invoking Sidekiq jobs when there are open database
168
+ transactions
169
+ test_files: []