Module: Sidekiq::TransactionGuard

Defined in:
lib/sidekiq/transaction_guard.rb,
lib/sidekiq/transaction_guard/rspec.rb,
lib/sidekiq/transaction_guard/railtie.rb,
lib/sidekiq/transaction_guard/version.rb,
lib/sidekiq/transaction_guard/minitest.rb,
lib/sidekiq/transaction_guard/middleware.rb,
lib/sidekiq/transaction_guard/database_cleaner.rb

Defined Under Namespace

Modules: DatabaseCleaner, MinitestHelper, RSpecIntegration Classes: InsideTransactionError, Middleware, Railtie

Constant Summary collapse

VALID_MODES =
[:warn, :stderr, :error, :disabled].freeze
VERSION =
File.read(File.expand_path("../../../../VERSION", __FILE__)).chomp.freeze

Class Method Summary collapse

Class Method Details

.add_connection_class(connection_class) ⇒ void

This method returns an undefined value.

Add a class that maintains its own connection pool to the connections being monitored for open transactions. You don't need to add ActiveRecord::Base or subclasses. Only the base class that establishes a new connection pool with a call to establish_connection needs to be added.

Parameters:

  • connection_class (Class)

    an ActiveRecord model class with its own connection pool



120
121
122
# File 'lib/sidekiq/transaction_guard.rb', line 120

def add_connection_class(connection_class)
  @lock.synchronize { @connection_classes << connection_class }
end

.begin_testingObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Start a testing context on the current thread without a block. This is used by test framework integrations (like the Minitest helper) that cannot wrap the entire test in a single block. Use testing instead whenever a block can be used. The returned state must be passed to end_testing when the test finishes.

This method only allocates the transaction tracking state; it does not capture a transaction level baseline. The caller is responsible for calling set_allowed_transaction_level once any setup that opens transactions (e.g. transactional fixtures) has run.

Returns:

  • (Object)

    opaque saved state to pass to end_testing



193
194
195
196
197
198
# File 'lib/sidekiq/transaction_guard.rb', line 193

def begin_testing
  var = :sidekiq_rails_transaction_guard
  saved_state = Thread.current[var]
  Thread.current[var] = (saved_state ? saved_state.dup : {})
  saved_state
end

.connection_classesArray<Class>

Return the classes that have been added via add_connection_class.

Returns:

  • (Array<Class>)


127
128
129
# File 'lib/sidekiq/transaction_guard.rb', line 127

def connection_classes
  ([ActiveRecord::Base] + @lock.synchronize { @connection_classes.to_a }).uniq
end

.default_modeSymbol

Return the globally configured mode, ignoring any thread local override.

Returns:

  • (Symbol)


70
71
72
# File 'lib/sidekiq/transaction_guard.rb', line 70

def default_mode
  @lock.synchronize { @mode }
end

.disable { ... } ⇒ Object

Disable the transaction guard within the provided block. This is useful in test environments when you want to setup data for your tests without worrying about transaction levels.

The guard is only disabled for the current thread so that jobs enqueued concurrently in other threads are still checked.

Yields:

  • the block to execute with the transaction guard disabled

Returns:

  • (Object)

    the return value of the block



153
154
155
156
157
158
159
160
161
# File 'lib/sidekiq/transaction_guard.rb', line 153

def disable
  save_mode = thread_local_mode
  begin
    self.thread_local_mode = :disabled
    yield
  ensure
    self.thread_local_mode = save_mode
  end
end

.end_testing(saved_state) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

End a testing context started with begin_testing.

Parameters:

  • saved_state (Object)

    the state returned by begin_testing



205
206
207
# File 'lib/sidekiq/transaction_guard.rb', line 205

def end_testing(saved_state)
  Thread.current[:sidekiq_rails_transaction_guard] = saved_state
end

.in_transaction?Boolean

Return true if any connection is currently inside of a transaction.

Returns:

  • (Boolean)


134
135
136
137
138
139
140
141
142
143
# File 'lib/sidekiq/transaction_guard.rb', line 134

def in_transaction?
  connection_classes.any? do |connection_class|
    connection = active_connection(connection_class)
    if connection
      connection.open_transactions > allowed_transaction_level(connection_class)
    else
      false
    end
  end
end

.init(mode: nil) ⇒ void

This method returns an undefined value.

Initialize Sidekiq::TransactionGuard by adding its client middleware to Sidekiq. The middleware is added to both the client and server configurations since jobs can also be enqueued from within other jobs running in the Sidekiq server process.

Parameters:

  • mode (Symbol, nil) (defaults to: nil)

    optionally set the global mode (see mode=)



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/sidekiq/transaction_guard.rb', line 29

def init(mode: nil)
  self.mode = mode if mode

  Sidekiq.configure_client do |config|
    add_client_middleware(config)
  end

  Sidekiq.configure_server do |config|
    add_client_middleware(config)
  end
end

.modeSymbol

Return the mode in effect for the current thread. This is the thread local mode if one has been set, otherwise the global mode.

Returns:

  • (Symbol)


63
64
65
# File 'lib/sidekiq/transaction_guard.rb', line 63

def mode
  thread_local_mode || default_mode
end

.mode=(symbol) ⇒ Symbol

Set the global mode to one of [:warn, :stderr, :error, :disabled]. The default mode is :warn. This controls the behavior of workers enqueued inside of transactions.

  • :warn - Log to Sidekiq.logger
  • :stderr - Log to STDERR
  • :error - Raise a Sidekiq::TransactionGuard::InsideTransactionError
  • :disabled - Allow workers inside of transactions

Parameters:

  • symbol (Symbol)

    one of :warn, :stderr, :error, or :disabled

Returns:

  • (Symbol)

    the mode that was set



51
52
53
54
55
56
57
# File 'lib/sidekiq/transaction_guard.rb', line 51

def mode=(symbol)
  if VALID_MODES.include?(symbol)
    @lock.synchronize { @mode = symbol }
  else
    raise ArgumentError.new("mode must be one of #{VALID_MODES.inspect}")
  end
end

.notify {|Hash| ... } ⇒ void

This method returns an undefined value.

Define the global notify block. This block will be called with a Sidekiq job hash for all jobs enqueued inside transactions if the mode is :warn or :stderr.

Yields:

  • (Hash)

    the Sidekiq job hash



102
103
104
# File 'lib/sidekiq/transaction_guard.rb', line 102

def notify(&block)
  @lock.synchronize { @notify = block }
end

.notify_blockProc?

Return the block set as the notify handler with a call to notify.

Returns:

  • (Proc, nil)

    the notify block, or nil if none has been set



109
110
111
# File 'lib/sidekiq/transaction_guard.rb', line 109

def notify_block
  @lock.synchronize { @notify }
end

.set_allowed_transaction_level(connection_classes, base_transaction_level = 0) ⇒ void

This method returns an undefined value.

This method needs to be called to set the allowed transaction level for a connection class (see add_connection_class for more info). The current transaction level for that class's connection will be set as the zero point. This method can only be called inside a block wrapped with the testing method.

Parameters:

  • connection_classes (Class, Array<Class>, Symbol)

    the connection class(es) to set the allowed transaction level for. If :all is provided, set the allowed transaction level for all connection classes set via add_connection_class.

  • base_transaction_level (Integer) (defaults to: 0)

    if provided, increment the allowed transaction level by this amount. This is used when using transactional fixtures to ignore the transaction opened within the test setup.



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/sidekiq/transaction_guard.rb', line 221

def set_allowed_transaction_level(connection_classes, base_transaction_level = 0)
  connection_counts = Thread.current[:sidekiq_rails_transaction_guard]
  unless connection_counts
    raise("set_allowed_transaction_level is only allowed inside a testing block")
  end

  connection_classes = self.connection_classes if connection_classes == :all
  Array(connection_classes).each do |connection_class|
    class_count = begin
      lease_connection(connection_class).open_transactions + base_transaction_level
    rescue ActiveRecord::ConnectionNotEstablished
      base_transaction_level
    end
    connection_counts[connection_class.name] = class_count
  end
end

.testing { ... } ⇒ Object

This method call needs to be wrapped around tests that use transactional fixtures. It sets up data structures used to track the number of open transactions. The current transaction level is automatically captured as the baseline so that any transactions opened by test setup (e.g. transactional fixtures) are ignored.

Yields:

  • the test block to execute

Returns:

  • (Object)

    the return value of the block



170
171
172
173
174
175
176
177
178
# File 'lib/sidekiq/transaction_guard.rb', line 170

def testing
  saved_state = begin_testing
  begin
    set_allowed_transaction_level(:all)
    yield
  ensure
    end_testing(saved_state)
  end
end

.thread_local_modeSymbol?

Return the mode override for the current thread, or nil if none is set.

Returns:

  • (Symbol, nil)


92
93
94
# File 'lib/sidekiq/transaction_guard.rb', line 92

def thread_local_mode
  Thread.current[:sidekiq_transaction_guard_mode]
end

.thread_local_mode=(symbol) ⇒ Symbol?

Set a mode override for the current thread only. This is used by disable and the test integrations so that changing the mode in one thread cannot affect jobs being enqueued concurrently in other threads. Set to nil to remove the override and fall back to the global mode.

Parameters:

  • symbol (Symbol, nil)

    one of :warn, :stderr, :error, :disabled, or nil

Returns:

  • (Symbol, nil)

    the mode that was set



81
82
83
84
85
86
87
# File 'lib/sidekiq/transaction_guard.rb', line 81

def thread_local_mode=(symbol)
  if symbol.nil? || VALID_MODES.include?(symbol)
    Thread.current[:sidekiq_transaction_guard_mode] = symbol
  else
    raise ArgumentError.new("mode must be one of #{VALID_MODES.inspect}")
  end
end