module Isolator
Isolator
detects unsafe operations performed within DB transactions.
Constants
- VERSION
Attributes
backtrace_cleaner[RW]
backtrace_length[RW]
debug_enabled[RW]
default_connection_id[RW]
default_threshold[RW]
state[RW]
Public Class Methods
adapters()
click to toggle source
# File lib/isolator.rb, line 166 def adapters @adapters ||= Isolator::SimpleHashie.new end
clear_transactions!()
click to toggle source
# File lib/isolator.rb, line 147 def clear_transactions! state[:transactions]&.clear end
config()
click to toggle source
# File lib/isolator.rb, line 39 def config @config ||= Configuration.new end
configure() { |config| ... }
click to toggle source
# File lib/isolator.rb, line 43 def configure yield config end
current_transactions(connection_id = default_connection_id.call)
click to toggle source
# File lib/isolator.rb, line 93 def current_transactions(connection_id = default_connection_id.call) state[:transactions]&.[](connection_id) || 0 end
decr_thresholds!()
click to toggle source
# File lib/isolator.rb, line 113 def decr_thresholds! self.default_threshold -= 1 return unless state[:thresholds] state[:thresholds].transform_values!(&:pred) debug!("Thresholds were decremented") end
decr_transactions!(connection_id = default_connection_id.call)
click to toggle source
# File lib/isolator.rb, line 137 def decr_transactions!(connection_id = default_connection_id.call) state[:transactions][connection_id] -= 1 finish! if current_transactions(connection_id) == (connection_threshold(connection_id) - 1) state[:transactions].delete(connection_id) if state[:transactions][connection_id].zero? debug!("Transaction closed for connection #{connection_id} (total: #{state[:transactions][connection_id]}, threshold: #{state[:thresholds]&.[](connection_id) || default_threshold})") end
disable() { || ... }
click to toggle source
Accepts block and disable Isolator
within
# File lib/isolator.rb, line 60 def disable return yield if disabled? res = nil begin disable! res = yield ensure enable! end res end
disable!()
click to toggle source
# File lib/isolator.rb, line 55 def disable! state[:disabled] = true end
disabled?()
click to toggle source
# File lib/isolator.rb, line 162 def disabled? state[:disabled] == true end
enable() { || ... }
click to toggle source
Accepts block and enable Isolator
within
# File lib/isolator.rb, line 73 def enable return yield if enabled? res = nil begin enable! res = yield ensure disable! end res end
enable!()
click to toggle source
# File lib/isolator.rb, line 51 def enable! state[:disabled] = false end
enabled?()
click to toggle source
# File lib/isolator.rb, line 158 def enabled? !disabled? end
incr_thresholds!()
click to toggle source
# File lib/isolator.rb, line 104 def incr_thresholds! self.default_threshold += 1 return unless state[:thresholds] state[:thresholds].transform_values!(&:succ) debug!("Thresholds were incremented") end
incr_transactions!(connection_id = default_connection_id.call)
click to toggle source
# File lib/isolator.rb, line 122 def incr_transactions!(connection_id = default_connection_id.call) state[:transactions] ||= Hash.new { |h, k| h[k] = 0 } state[:transactions][connection_id] += 1 # Workaround to track threshold changes made before opening a connection pending_threshold = state[:thresholds]&.delete(0) if pending_threshold state[:thresholds][connection_id] = pending_threshold end debug!("Transaction opened for connection #{connection_id} (total: #{state[:transactions][connection_id]}, threshold: #{state[:thresholds]&.fetch(connection_id, default_threshold)})") start! if current_transactions(connection_id) == connection_threshold(connection_id) end
load_ignore_config(path)
click to toggle source
# File lib/isolator.rb, line 170 def load_ignore_config(path) warn "[DEPRECATION] `load_ignore_config` is deprecated. Please use `Isolator::Ignorer.prepare` instead." Isolator::Ignorer.prepare(path: path) end
notify(exception:, backtrace:)
click to toggle source
# File lib/isolator.rb, line 47 def notify(exception:, backtrace:) Notifier.new(exception, backtrace).call end
set_connection_threshold(val, connection_id = default_connection_id.call)
click to toggle source
# File lib/isolator.rb, line 97 def set_connection_threshold(val, connection_id = default_connection_id.call) state[:thresholds] ||= Hash.new { |h, k| h[k] = Isolator.default_threshold } state[:thresholds][connection_id] = val debug!("Threshold value was changed for connection #{connection_id}: #{val}") end
transactions_threshold(connection_id = default_connection_id.call)
click to toggle source
# File lib/isolator.rb, line 89 def transactions_threshold(connection_id = default_connection_id.call) connection_threshold(connection_id) end
transactions_threshold=(val)
click to toggle source
# File lib/isolator.rb, line 85 def transactions_threshold=(val) set_connection_threshold(val) end
within_transaction?()
click to toggle source
# File lib/isolator.rb, line 151 def within_transaction? state[:transactions]&.each do |connection_id, transaction_count| return true if transaction_count >= connection_threshold(connection_id) end false end
Private Class Methods
colorize_debug(msg)
click to toggle source
# File lib/isolator.rb, line 206 def colorize_debug(msg) return msg unless $stdout.tty? "\u001b[31;1m#{msg}\u001b[0m" end
connection_threshold(connection_id)
click to toggle source
# File lib/isolator.rb, line 184 def connection_threshold(connection_id) state[:thresholds]&.[](connection_id) || default_threshold end
debug!(msg)
click to toggle source
# File lib/isolator.rb, line 188 def debug!(msg) return unless debug_enabled msg = "[ISOLATOR DEBUG] #{msg}" if backtrace_cleaner && backtrace_length.positive? source = extract_source_location(caller) msg = "#{msg}\n ↳ #{source.join("\n")}" unless source.empty? end $stdout.puts(colorize_debug(msg)) end
extract_source_location(locations)
click to toggle source
# File lib/isolator.rb, line 201 def extract_source_location(locations) backtrace_cleaner.call(locations.lazy) .take(backtrace_length).to_a end