sidekiq-transaction_guard 1.1.1 → 1.1.2
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 +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +17 -5
- data/VERSION +1 -1
- data/lib/sidekiq/transaction_guard/middleware.rb +3 -2
- data/lib/sidekiq/transaction_guard/minitest.rb +40 -35
- data/lib/sidekiq/transaction_guard/rspec.rb +68 -30
- data/lib/sidekiq/transaction_guard.rb +120 -21
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e78c153bc733cc296bd92ee4ef71be1999660724aed0a4421c68a86bea98233
|
|
4
|
+
data.tar.gz: '095f61285d5cb8d7aade4f5c343ea7a6d7e3b548a3ec8e6a128dae5e0d64e740'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 386abe0d3a8b40f22358dd7e30bd808a6e9837e5dc0d34731b5693ee8a02375edee77a425c56e97efc3deec356207aaa7b247618ceda42d25d9da9756d653e7f
|
|
7
|
+
data.tar.gz: c4c88c36fc2b38e6ddbbec3933873f8983ba9a389d9d1554c4913e1ccbf160ba5ab1768a73e3120cb9fca40a92e5dac159518b627452c3ca5eae8fe517ae1453
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 1.1.2
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `Sidekiq::TransactionGuard.init` now registers the client middleware in the Sidekiq server process as well. Previously jobs enqueued from within other jobs bypassed the transaction guard entirely because `Sidekiq.configure_client` is a no-op in server processes.
|
|
12
|
+
- Fixed the Minitest helper so the transaction tracking set up by `Sidekiq::TransactionGuard.testing` remains active for the duration of the test. Previously the testing context was torn down when `setup` returned, so the transaction level snapshot was discarded before the test body ran and tests using transactional fixtures would raise false positive errors on every enqueue. The helper now uses Minitest's `before_setup`/`after_teardown` lifecycle hooks and takes the snapshot after all other setup hooks (including transactional fixtures) have run.
|
|
13
|
+
- The Minitest helper no longer permanently changes the global mode when it is included and no longer defines `setup`/`teardown` methods that could be silently overridden by test classes defining their own.
|
|
14
|
+
- `Sidekiq::TransactionGuard.disable` and the RSpec/Minitest test integrations now use a thread local mode override instead of mutating the global mode. This fixes race conditions where disabling the guard in one thread would disable it for all threads, and where concurrent save/restore of the global mode could leave it in the wrong state.
|
|
15
|
+
- The RSpec integration now registers its per-example hooks at the example group level so the transaction level snapshot is taken after rspec-rails opens the transactional fixture transaction instead of before it.
|
|
16
|
+
- Worker-level `transaction_guard` sidekiq options are now honored when the value is a string as well as a symbol.
|
|
17
|
+
- Connection errors from registered connection classes without an established connection no longer raise from `in_transaction?`.
|
|
18
|
+
- Use `lease_connection` instead of the deprecated `connection` method on ActiveRecord 7.2+.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `Sidekiq::TransactionGuard.thread_local_mode` and `thread_local_mode=` to override the mode for the current thread only.
|
|
23
|
+
- `Sidekiq::TransactionGuard.default_mode` to read the globally configured mode, ignoring any thread local override.
|
|
24
|
+
|
|
7
25
|
## 1.1.1
|
|
8
26
|
|
|
9
27
|
### Changed
|
data/README.md
CHANGED
|
@@ -70,9 +70,11 @@ For non-Rails applications, you need to manually add the middleware in your appl
|
|
|
70
70
|
```ruby
|
|
71
71
|
require 'sidekiq/transaction_guard'
|
|
72
72
|
|
|
73
|
-
Sidekiq::TransactionGuard
|
|
73
|
+
Sidekiq::TransactionGuard.init
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
This registers the client middleware for both client processes and the Sidekiq server process, so jobs enqueued from within other jobs are checked as well.
|
|
77
|
+
|
|
76
78
|
### Mode
|
|
77
79
|
|
|
78
80
|
You can set the mode at any time. The mode can be one of `[:warn, :stderr, :error, :disabled]`.
|
|
@@ -94,7 +96,13 @@ Sidekiq::TransactionGuard.mode = :disabled
|
|
|
94
96
|
You can set the mode when initializing the middleware:
|
|
95
97
|
|
|
96
98
|
```ruby
|
|
97
|
-
Sidekiq::TransactionGuard
|
|
99
|
+
Sidekiq::TransactionGuard.init(mode: :error)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
You can also override the mode for the current thread only. This does not affect jobs being enqueued concurrently in other threads. Set it to `nil` to remove the override.
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
Sidekiq::TransactionGuard.thread_local_mode = :disabled
|
|
98
106
|
```
|
|
99
107
|
|
|
100
108
|
You can also set the mode on individual worker classes with `sidekiq_options transaction_guard: mode`. The worker-specific mode will override the global mode.
|
|
@@ -175,7 +183,9 @@ If you're using RSpec, you can use the built-in RSpec helper to automatically se
|
|
|
175
183
|
require 'sidekiq/transaction_guard/rspec'
|
|
176
184
|
```
|
|
177
185
|
|
|
178
|
-
|
|
186
|
+
Require this file after `rspec-rails` (or anything else that opens transactions in its setup hooks) so that the transaction level snapshot is taken after those transactions are opened.
|
|
187
|
+
|
|
188
|
+
Examples run with the `:error` mode by default. You can control the mode on a per-spec basis with a metadata tag. The value can be a mode symbol, `false` to disable the guard, or `:default` to use the globally configured mode. For example:
|
|
179
189
|
|
|
180
190
|
```ruby
|
|
181
191
|
RSpec.describe "Some feature", sidekiq_transaction_guard: :disabled do
|
|
@@ -203,7 +213,7 @@ If you're using Minitest with `ActiveSupport::TestCase` (Rails default), you can
|
|
|
203
213
|
require 'sidekiq/transaction_guard/minitest'
|
|
204
214
|
```
|
|
205
215
|
|
|
206
|
-
|
|
216
|
+
Tests run with the `:error` mode. The guard is disabled while `setup do` callbacks and teardown hooks run, and the transaction level snapshot is taken after all other setup hooks (including transactional fixtures) have completed.
|
|
207
217
|
|
|
208
218
|
If you're using plain Minitest (without `ActiveSupport::TestCase`), you can manually include the helper module:
|
|
209
219
|
|
|
@@ -223,7 +233,7 @@ Alternatively, you can manually use the `testing` method with minitest-hooks:
|
|
|
223
233
|
class MyTests < Minitest::Test
|
|
224
234
|
# Using minitest-hooks gem
|
|
225
235
|
def around(&block)
|
|
226
|
-
Sidekiq::TransactionGuard.testing
|
|
236
|
+
Sidekiq::TransactionGuard.testing do
|
|
227
237
|
block.call
|
|
228
238
|
end
|
|
229
239
|
end
|
|
@@ -240,6 +250,8 @@ Sidekiq::TransactionGuard.disable do
|
|
|
240
250
|
end
|
|
241
251
|
```
|
|
242
252
|
|
|
253
|
+
The guard is only disabled for the current thread, so jobs enqueued concurrently in other threads are still checked.
|
|
254
|
+
|
|
243
255
|
## Installation
|
|
244
256
|
|
|
245
257
|
Add this line to your application's Gemfile:
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.2
|
|
@@ -24,7 +24,9 @@ module Sidekiq
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
26
|
def worker_mode(job)
|
|
27
|
-
read_sidekiq_option(job, :transaction_guard)
|
|
27
|
+
mode = read_sidekiq_option(job, :transaction_guard)
|
|
28
|
+
mode = mode.to_sym if mode.is_a?(String)
|
|
29
|
+
mode || Sidekiq::TransactionGuard.mode
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
def in_transaction?
|
|
@@ -47,7 +49,6 @@ module Sidekiq
|
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def read_sidekiq_option(job, option_name)
|
|
50
|
-
# options = worker_class.sidekiq_options_hash
|
|
51
52
|
job[option_name.to_s]
|
|
52
53
|
end
|
|
53
54
|
|
|
@@ -2,12 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
require "minitest"
|
|
4
4
|
|
|
5
|
+
require "sidekiq/transaction_guard"
|
|
6
|
+
|
|
5
7
|
module Sidekiq
|
|
6
8
|
module TransactionGuard
|
|
7
9
|
# Minitest helper module for testing with Sidekiq::TransactionGuard.
|
|
8
10
|
#
|
|
9
|
-
# Include this module in your test class to automatically
|
|
10
|
-
#
|
|
11
|
+
# Include this module in your test class to automatically set up transaction
|
|
12
|
+
# tracking for each test. If `ActiveSupport::TestCase` is defined when this
|
|
13
|
+
# file is required, the module is included there automatically.
|
|
14
|
+
#
|
|
15
|
+
# The integration uses Minitest's `before_setup`/`after_teardown` lifecycle
|
|
16
|
+
# hooks. The transaction level snapshot is taken after all other setup hooks
|
|
17
|
+
# have run (including transactional fixtures), so transactions opened by test
|
|
18
|
+
# setup are ignored, and the snapshot stays in effect for the entire test.
|
|
19
|
+
# The guard runs in `:error` mode during the test and is disabled while
|
|
20
|
+
# setup and teardown hooks run.
|
|
11
21
|
#
|
|
12
22
|
# @example
|
|
13
23
|
# class MyTest < Minitest::Test
|
|
@@ -18,46 +28,41 @@ module Sidekiq
|
|
|
18
28
|
# end
|
|
19
29
|
# end
|
|
20
30
|
module MinitestHelper
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
def before_setup
|
|
32
|
+
@sidekiq_transaction_guard_saved_mode = Sidekiq::TransactionGuard.thread_local_mode
|
|
33
|
+
@sidekiq_transaction_guard_saved_state = Sidekiq::TransactionGuard.begin_testing
|
|
34
|
+
# Keep the guard disabled while other before_setup hooks (like
|
|
35
|
+
# transactional fixtures) and setup callbacks run.
|
|
36
|
+
Sidekiq::TransactionGuard.thread_local_mode = :disabled
|
|
37
|
+
super
|
|
38
|
+
# Take the snapshot now that all other setup hooks have run so that
|
|
39
|
+
# transactions opened during setup are treated as the baseline.
|
|
40
|
+
Sidekiq::TransactionGuard.set_allowed_transaction_level(:all)
|
|
41
|
+
Sidekiq::TransactionGuard.thread_local_mode = :error
|
|
42
|
+
end
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@sidekiq_transaction_guard_testing_block = true
|
|
32
|
-
super
|
|
33
|
-
end
|
|
34
|
-
end
|
|
44
|
+
def before_teardown
|
|
45
|
+
Sidekiq::TransactionGuard.thread_local_mode = :disabled
|
|
46
|
+
super
|
|
47
|
+
end
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
def after_teardown
|
|
50
|
+
super
|
|
51
|
+
ensure
|
|
52
|
+
if defined?(@sidekiq_transaction_guard_saved_state)
|
|
53
|
+
Sidekiq::TransactionGuard.end_testing(@sidekiq_transaction_guard_saved_state)
|
|
54
|
+
end
|
|
55
|
+
if defined?(@sidekiq_transaction_guard_saved_mode)
|
|
56
|
+
Sidekiq::TransactionGuard.thread_local_mode = @sidekiq_transaction_guard_saved_mode
|
|
40
57
|
end
|
|
41
58
|
end
|
|
42
59
|
end
|
|
43
60
|
end
|
|
44
61
|
end
|
|
45
62
|
|
|
46
|
-
# If using ActiveSupport::TestCase, automatically include the helper
|
|
63
|
+
# If using ActiveSupport::TestCase, automatically include the helper.
|
|
47
64
|
if defined?(ActiveSupport::TestCase)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Sidekiq::TransactionGuard.mode = :error
|
|
52
|
-
|
|
53
|
-
Sidekiq::TransactionGuard.testing do
|
|
54
|
-
super
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def teardown
|
|
59
|
-
super
|
|
60
|
-
Sidekiq::TransactionGuard.mode = @sidekiq_transaction_guard_saved_mode
|
|
61
|
-
end
|
|
62
|
-
end
|
|
65
|
+
# Included with `send` so that YARD doesn't try to statically resolve the
|
|
66
|
+
# ActiveSupport::TestCase namespace when generating documentation.
|
|
67
|
+
ActiveSupport::TestCase.send(:include, Sidekiq::TransactionGuard::MinitestHelper)
|
|
63
68
|
end
|
|
@@ -1,46 +1,84 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require "sidekiq/transaction_guard"
|
|
4
|
+
|
|
5
|
+
module Sidekiq
|
|
6
|
+
module TransactionGuard
|
|
7
|
+
# RSpec integration. Requiring this file wraps every example in a
|
|
8
|
+
# `Sidekiq::TransactionGuard.testing` block and manages the transaction guard
|
|
9
|
+
# mode with a thread local override so parallel test threads can't interfere
|
|
10
|
+
# with each other.
|
|
11
|
+
#
|
|
12
|
+
# The per example mode can be controlled with the `:sidekiq_transaction_guard`
|
|
13
|
+
# metadata tag. The value can be a mode symbol, `false` to disable the guard,
|
|
14
|
+
# or `:default` to use the globally configured mode. Untagged examples run
|
|
15
|
+
# with the `:error` mode.
|
|
16
|
+
#
|
|
17
|
+
# This file should be required after rspec-rails (or any other library that
|
|
18
|
+
# opens transactions in its setup hooks) so that the transaction level
|
|
19
|
+
# snapshot taken before each example runs after those transactions are opened.
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
module RSpecIntegration
|
|
23
|
+
def self.included(example_group)
|
|
24
|
+
# Nested example groups inherit hooks from their parent group, so only
|
|
25
|
+
# register the hooks on the outermost group that includes this module.
|
|
26
|
+
return if example_group.is_a?(Class) && example_group.superclass.include?(RSpecIntegration)
|
|
27
|
+
|
|
28
|
+
# These are registered as group level hooks so they run after hooks added
|
|
29
|
+
# by modules included earlier — in particular after rspec-rails has opened
|
|
30
|
+
# the transactional fixture transaction — so the snapshot taken here
|
|
31
|
+
# treats transactions opened by test setup as the baseline.
|
|
32
|
+
example_group.before do |example|
|
|
33
|
+
Sidekiq::TransactionGuard::RSpecIntegration.apply_example_mode(example)
|
|
34
|
+
Sidekiq::TransactionGuard.set_allowed_transaction_level(:all)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
example_group.after do
|
|
38
|
+
# Disable the guard before fixture rollback and other teardown hooks run.
|
|
39
|
+
Sidekiq::TransactionGuard.thread_local_mode = :disabled
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.apply_example_mode(example)
|
|
44
|
+
metadata = example.metadata
|
|
45
|
+
if metadata.key?(:sidekiq_transaction_guard)
|
|
46
|
+
mode = metadata[:sidekiq_transaction_guard]
|
|
47
|
+
mode = :disabled if mode == false
|
|
48
|
+
mode = nil if mode == :default
|
|
49
|
+
mode = :error unless mode.nil? || mode.is_a?(Symbol)
|
|
50
|
+
Sidekiq::TransactionGuard.thread_local_mode = mode
|
|
51
|
+
elsif Sidekiq::TransactionGuard.thread_local_mode == :disabled
|
|
52
|
+
# Apply the default mode only if an earlier hook hasn't already set one.
|
|
53
|
+
Sidekiq::TransactionGuard.thread_local_mode = :error
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
5
59
|
|
|
6
|
-
|
|
60
|
+
RSpec.configure do |config|
|
|
61
|
+
# Disable the guard for code running outside of examples, such as suite level
|
|
62
|
+
# setup and teardown.
|
|
7
63
|
config.before(:suite) do
|
|
8
|
-
|
|
9
|
-
Sidekiq::TransactionGuard.mode = :disabled
|
|
64
|
+
Sidekiq::TransactionGuard.thread_local_mode = :disabled
|
|
10
65
|
end
|
|
11
66
|
|
|
12
67
|
config.after(:suite) do
|
|
13
|
-
Sidekiq::TransactionGuard.
|
|
68
|
+
Sidekiq::TransactionGuard.thread_local_mode = nil
|
|
14
69
|
end
|
|
15
70
|
|
|
16
|
-
# Wrap each example in a block
|
|
71
|
+
# Wrap each example in a testing block and keep the guard disabled except
|
|
72
|
+
# while the example itself is running.
|
|
17
73
|
config.around do |example|
|
|
18
|
-
|
|
74
|
+
saved_mode = Sidekiq::TransactionGuard.thread_local_mode
|
|
19
75
|
begin
|
|
20
|
-
|
|
21
|
-
Sidekiq::TransactionGuard.
|
|
22
|
-
Sidekiq::TransactionGuard.testing do
|
|
23
|
-
example.run
|
|
24
|
-
end
|
|
76
|
+
Sidekiq::TransactionGuard.thread_local_mode = :disabled
|
|
77
|
+
Sidekiq::TransactionGuard.testing { example.run }
|
|
25
78
|
ensure
|
|
26
|
-
Sidekiq::TransactionGuard.
|
|
79
|
+
Sidekiq::TransactionGuard.thread_local_mode = saved_mode
|
|
27
80
|
end
|
|
28
81
|
end
|
|
29
82
|
|
|
30
|
-
|
|
31
|
-
# transactional fixtures) has run so that setup transactions are ignored.
|
|
32
|
-
config.before(:each) do |example|
|
|
33
|
-
mode = example.metadata[:sidekiq_transaction_guard]
|
|
34
|
-
mode = :disabled if mode == false
|
|
35
|
-
mode = global_sidekiq_transaction_guard_mode if mode == :default
|
|
36
|
-
mode = :error unless mode.is_a?(Symbol)
|
|
37
|
-
Sidekiq::TransactionGuard.mode = mode if Sidekiq::TransactionGuard.mode == :disabled
|
|
38
|
-
|
|
39
|
-
Sidekiq::TransactionGuard.set_allowed_transaction_level(:all)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Restore the disabled mode after example so test teardown doesn't raise errors about transaction levels.
|
|
43
|
-
config.after(:each) do
|
|
44
|
-
Sidekiq::TransactionGuard.mode = :disabled
|
|
45
|
-
end
|
|
83
|
+
config.include Sidekiq::TransactionGuard::RSpecIntegration
|
|
46
84
|
end
|
|
@@ -19,18 +19,22 @@ module Sidekiq
|
|
|
19
19
|
class << self
|
|
20
20
|
VALID_MODES = [:warn, :stderr, :error, :disabled].freeze
|
|
21
21
|
|
|
22
|
-
#
|
|
22
|
+
# Initialize Sidekiq::TransactionGuard by adding its client middleware to
|
|
23
|
+
# Sidekiq. The middleware is added to both the client and server
|
|
24
|
+
# configurations since jobs can also be enqueued from within other jobs
|
|
25
|
+
# running in the Sidekiq server process.
|
|
23
26
|
#
|
|
27
|
+
# @param mode [Symbol, nil] optionally set the global mode (see `mode=`)
|
|
24
28
|
# @return [void]
|
|
25
29
|
def init(mode: nil)
|
|
26
30
|
self.mode = mode if mode
|
|
27
31
|
|
|
28
32
|
Sidekiq.configure_client do |config|
|
|
29
|
-
config
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
add_client_middleware(config)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Sidekiq.configure_server do |config|
|
|
37
|
+
add_client_middleware(config)
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
|
|
@@ -46,16 +50,48 @@ module Sidekiq
|
|
|
46
50
|
# @return [Symbol] the mode that was set
|
|
47
51
|
def mode=(symbol)
|
|
48
52
|
if VALID_MODES.include?(symbol)
|
|
49
|
-
@mode = symbol
|
|
53
|
+
@lock.synchronize { @mode = symbol }
|
|
50
54
|
else
|
|
51
55
|
raise ArgumentError.new("mode must be one of #{VALID_MODES.inspect}")
|
|
52
56
|
end
|
|
53
57
|
end
|
|
54
58
|
|
|
55
|
-
# Return the current
|
|
59
|
+
# Return the mode in effect for the current thread. This is the thread local
|
|
60
|
+
# mode if one has been set, otherwise the global mode.
|
|
61
|
+
#
|
|
62
|
+
# @return [Symbol]
|
|
63
|
+
def mode
|
|
64
|
+
thread_local_mode || default_mode
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Return the globally configured mode, ignoring any thread local override.
|
|
56
68
|
#
|
|
57
69
|
# @return [Symbol]
|
|
58
|
-
|
|
70
|
+
def default_mode
|
|
71
|
+
@lock.synchronize { @mode }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Set a mode override for the current thread only. This is used by `disable`
|
|
75
|
+
# and the test integrations so that changing the mode in one thread cannot
|
|
76
|
+
# affect jobs being enqueued concurrently in other threads. Set to `nil` to
|
|
77
|
+
# remove the override and fall back to the global mode.
|
|
78
|
+
#
|
|
79
|
+
# @param symbol [Symbol, nil] one of `:warn`, `:stderr`, `:error`, `:disabled`, or nil
|
|
80
|
+
# @return [Symbol, nil] the mode that was set
|
|
81
|
+
def thread_local_mode=(symbol)
|
|
82
|
+
if symbol.nil? || VALID_MODES.include?(symbol)
|
|
83
|
+
Thread.current[:sidekiq_transaction_guard_mode] = symbol
|
|
84
|
+
else
|
|
85
|
+
raise ArgumentError.new("mode must be one of #{VALID_MODES.inspect}")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Return the mode override for the current thread, or nil if none is set.
|
|
90
|
+
#
|
|
91
|
+
# @return [Symbol, nil]
|
|
92
|
+
def thread_local_mode
|
|
93
|
+
Thread.current[:sidekiq_transaction_guard_mode]
|
|
94
|
+
end
|
|
59
95
|
|
|
60
96
|
# Define the global notify block. This block will be called with a Sidekiq
|
|
61
97
|
# job hash for all jobs enqueued inside transactions if the mode is `:warn`
|
|
@@ -64,14 +100,14 @@ module Sidekiq
|
|
|
64
100
|
# @yield [Hash] the Sidekiq job hash
|
|
65
101
|
# @return [void]
|
|
66
102
|
def notify(&block)
|
|
67
|
-
@notify = block
|
|
103
|
+
@lock.synchronize { @notify = block }
|
|
68
104
|
end
|
|
69
105
|
|
|
70
106
|
# Return the block set as the notify handler with a call to `notify`.
|
|
71
107
|
#
|
|
72
108
|
# @return [Proc, nil] the notify block, or nil if none has been set
|
|
73
109
|
def notify_block
|
|
74
|
-
@notify
|
|
110
|
+
@lock.synchronize { @notify }
|
|
75
111
|
end
|
|
76
112
|
|
|
77
113
|
# Add a class that maintains its own connection pool to the connections
|
|
@@ -97,8 +133,7 @@ module Sidekiq
|
|
|
97
133
|
# @return [Boolean]
|
|
98
134
|
def in_transaction?
|
|
99
135
|
connection_classes.any? do |connection_class|
|
|
100
|
-
|
|
101
|
-
connection = connection_class.connection if connection_pool.active_connection?
|
|
136
|
+
connection = active_connection(connection_class)
|
|
102
137
|
if connection
|
|
103
138
|
connection.open_transactions > allowed_transaction_level(connection_class)
|
|
104
139
|
else
|
|
@@ -110,15 +145,18 @@ module Sidekiq
|
|
|
110
145
|
# Disable the transaction guard within the provided block. This is useful in test environments when you want to
|
|
111
146
|
# setup data for your tests without worrying about transaction levels.
|
|
112
147
|
#
|
|
148
|
+
# The guard is only disabled for the current thread so that jobs enqueued
|
|
149
|
+
# concurrently in other threads are still checked.
|
|
150
|
+
#
|
|
113
151
|
# @yield the block to execute with the transaction guard disabled
|
|
114
152
|
# @return [Object] the return value of the block
|
|
115
153
|
def disable
|
|
116
|
-
save_mode =
|
|
154
|
+
save_mode = thread_local_mode
|
|
117
155
|
begin
|
|
118
|
-
self.
|
|
156
|
+
self.thread_local_mode = :disabled
|
|
119
157
|
yield
|
|
120
158
|
ensure
|
|
121
|
-
self.
|
|
159
|
+
self.thread_local_mode = save_mode
|
|
122
160
|
end
|
|
123
161
|
end
|
|
124
162
|
|
|
@@ -130,17 +168,44 @@ module Sidekiq
|
|
|
130
168
|
# @yield the test block to execute
|
|
131
169
|
# @return [Object] the return value of the block
|
|
132
170
|
def testing
|
|
133
|
-
|
|
134
|
-
save_val = Thread.current[var]
|
|
171
|
+
saved_state = begin_testing
|
|
135
172
|
begin
|
|
136
|
-
Thread.current[var] = (save_val ? save_val.dup : {})
|
|
137
173
|
set_allowed_transaction_level(:all)
|
|
138
174
|
yield
|
|
139
175
|
ensure
|
|
140
|
-
|
|
176
|
+
end_testing(saved_state)
|
|
141
177
|
end
|
|
142
178
|
end
|
|
143
179
|
|
|
180
|
+
# Start a testing context on the current thread without a block. This is used
|
|
181
|
+
# by test framework integrations (like the Minitest helper) that cannot wrap
|
|
182
|
+
# the entire test in a single block. Use `testing` instead whenever a block
|
|
183
|
+
# can be used. The returned state must be passed to `end_testing` when the
|
|
184
|
+
# test finishes.
|
|
185
|
+
#
|
|
186
|
+
# This method only allocates the transaction tracking state; it does not
|
|
187
|
+
# capture a transaction level baseline. The caller is responsible for calling
|
|
188
|
+
# `set_allowed_transaction_level` once any setup that opens transactions
|
|
189
|
+
# (e.g. transactional fixtures) has run.
|
|
190
|
+
#
|
|
191
|
+
# @api private
|
|
192
|
+
# @return [Object] opaque saved state to pass to `end_testing`
|
|
193
|
+
def begin_testing
|
|
194
|
+
var = :sidekiq_rails_transaction_guard
|
|
195
|
+
saved_state = Thread.current[var]
|
|
196
|
+
Thread.current[var] = (saved_state ? saved_state.dup : {})
|
|
197
|
+
saved_state
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# End a testing context started with `begin_testing`.
|
|
201
|
+
#
|
|
202
|
+
# @api private
|
|
203
|
+
# @param saved_state [Object] the state returned by `begin_testing`
|
|
204
|
+
# @return [void]
|
|
205
|
+
def end_testing(saved_state)
|
|
206
|
+
Thread.current[:sidekiq_rails_transaction_guard] = saved_state
|
|
207
|
+
end
|
|
208
|
+
|
|
144
209
|
# This method needs to be called to set the allowed transaction level for a connection
|
|
145
210
|
# class (see `add_connection_class` for more info). The current transaction level
|
|
146
211
|
# for that class's connection will be set as the zero point. This method can only
|
|
@@ -161,7 +226,11 @@ module Sidekiq
|
|
|
161
226
|
|
|
162
227
|
connection_classes = self.connection_classes if connection_classes == :all
|
|
163
228
|
Array(connection_classes).each do |connection_class|
|
|
164
|
-
class_count =
|
|
229
|
+
class_count = begin
|
|
230
|
+
lease_connection(connection_class).open_transactions + base_transaction_level
|
|
231
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
|
232
|
+
base_transaction_level
|
|
233
|
+
end
|
|
165
234
|
connection_counts[connection_class.name] = class_count
|
|
166
235
|
end
|
|
167
236
|
end
|
|
@@ -172,6 +241,35 @@ module Sidekiq
|
|
|
172
241
|
connection_counts = Thread.current[:sidekiq_rails_transaction_guard]
|
|
173
242
|
(connection_counts && connection_counts[connection_class.name]) || 0
|
|
174
243
|
end
|
|
244
|
+
|
|
245
|
+
def add_client_middleware(config)
|
|
246
|
+
config.client_middleware do |chain|
|
|
247
|
+
unless chain.exists?(Sidekiq::TransactionGuard::Middleware)
|
|
248
|
+
chain.add Sidekiq::TransactionGuard::Middleware
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Return the connection for the class only if the current thread already has
|
|
254
|
+
# one checked out. Checking out a new connection here would be pointless (a
|
|
255
|
+
# freshly checked out connection can't be inside an application transaction)
|
|
256
|
+
# and would tie up connections from pools the code isn't even using.
|
|
257
|
+
def active_connection(connection_class)
|
|
258
|
+
connection = nil
|
|
259
|
+
pool = connection_class.connection_pool
|
|
260
|
+
connection = lease_connection(connection_class) if pool.active_connection?
|
|
261
|
+
connection
|
|
262
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
|
263
|
+
nil
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def lease_connection(connection_class)
|
|
267
|
+
if connection_class.respond_to?(:lease_connection)
|
|
268
|
+
connection_class.lease_connection
|
|
269
|
+
else
|
|
270
|
+
connection_class.connection
|
|
271
|
+
end
|
|
272
|
+
end
|
|
175
273
|
end
|
|
176
274
|
end
|
|
177
275
|
end
|
|
@@ -181,6 +279,7 @@ if defined?(Rails::Railtie)
|
|
|
181
279
|
end
|
|
182
280
|
|
|
183
281
|
# Configure the default transaction guard mode for known testing environments.
|
|
282
|
+
# In a Rails application the Railtie initializer will override this default.
|
|
184
283
|
if ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
|
|
185
284
|
Sidekiq::TransactionGuard.mode = :stderr
|
|
186
285
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sidekiq-transaction_guard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '0'
|
|
96
96
|
requirements: []
|
|
97
|
-
rubygems_version:
|
|
97
|
+
rubygems_version: 4.0.3
|
|
98
98
|
specification_version: 4
|
|
99
99
|
summary: Protect from accidentally invoking Sidekiq jobs when there are open database
|
|
100
100
|
transactions
|