Class: RaceGuard::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/race_guard/configuration.rb

Overview

Per-process configuration. Mutations are protected by a Mutex.

Constant Summary collapse

DEFAULT_ENVIRONMENTS =
%i[development test].freeze
DEFAULT_SEVERITY =
:info

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

rubocop:disable Metrics/MethodLength



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/race_guard/configuration.rb', line 15

def initialize
  @mutex = Mutex.new
  @enabled = Set.new
  @enabled_rules = Set.new
  @default_severity = DEFAULT_SEVERITY
  @severities = {}
  @environments = DEFAULT_ENVIRONMENTS.dup
  @reporters = []
  @protect_detectors = []
  @db_lock_read_modify_write_classes = Set.new
  @shared_state_memo_globs = []
  @distributed_lock_store = nil
  @distributed_redis_client = nil
  @distributed_skip_behavior = :nil
  @distributed_reentrancy = :skip
  @distributed_key_prefix = nil
  @distributed_degrade_silently = false
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/race_guard/configuration.rb', line 94

def active?
  @mutex.synchronize { @environments.include?(current_environment) }
end

#add_protect_detector(detector) ⇒ Object



117
118
119
120
# File 'lib/race_guard/configuration.rb', line 117

def add_protect_detector(detector)
  @mutex.synchronize { @protect_detectors << detector }
  self
end

#add_reporter(reporter) ⇒ Object



98
99
100
101
# File 'lib/race_guard/configuration.rb', line 98

def add_reporter(reporter)
  @mutex.synchronize { @reporters << reporter }
  self
end

#clear_protect_detectorsObject



127
128
129
130
# File 'lib/race_guard/configuration.rb', line 127

def clear_protect_detectors
  @mutex.synchronize { @protect_detectors.clear }
  self
end

#clear_reportersObject



108
109
110
111
# File 'lib/race_guard/configuration.rb', line 108

def clear_reporters
  @mutex.synchronize { @reporters.clear }
  self
end

#current_environmentObject



242
243
244
245
# File 'lib/race_guard/configuration.rb', line 242

def current_environment
  raw = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
  raw.downcase.to_sym
end

#db_lock_read_modify_write_models(*klasses) ⇒ Object

Classes (e.g. ActiveRecord models) to audit for read-modify-write patterns (Epic 4.1). Empty by default: no read tracking / write correlation.



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/race_guard/configuration.rb', line 149

def db_lock_read_modify_write_models(*klasses)
  if klasses.compact.empty?
    return @mutex.synchronize do
      @db_lock_read_modify_write_classes.to_a
    end
  end

  flat = klasses.length == 1 && klasses.first.is_a?(Array) ? klasses.first : klasses
  @mutex.synchronize { @db_lock_read_modify_write_classes = Set.new(flat.compact) }
  self
end

#db_lock_read_modify_write_tracks?(klass) ⇒ Boolean

Returns:

  • (Boolean)


161
162
163
164
165
166
# File 'lib/race_guard/configuration.rb', line 161

def db_lock_read_modify_write_tracks?(klass)
  k = klass
  return false unless k.is_a?(Class)

  @mutex.synchronize { @db_lock_read_modify_write_classes.include?(k) }
end

#disable(name) ⇒ Object



41
42
43
44
45
# File 'lib/race_guard/configuration.rb', line 41

def disable(name)
  sym = name.to_sym
  @mutex.synchronize { @enabled.delete(sym) }
  self
end

#disable_rule(name) ⇒ Object



62
63
64
65
66
# File 'lib/race_guard/configuration.rb', line 62

def disable_rule(name)
  sym = name.to_sym
  @mutex.synchronize { @enabled_rules.delete(sym) }
  self
end

#distributed_degrade_silently(*args) ⇒ Object

When true, missing store / Redis errors run the block without a lock and omit error reports.



230
231
232
233
234
235
236
# File 'lib/race_guard/configuration.rb', line 230

def distributed_degrade_silently(*args)
  return @mutex.synchronize { @distributed_degrade_silently } if args.empty?

  v = args.first
  @mutex.synchronize { @distributed_degrade_silently = (v == true) }
  self
end

#distributed_key_prefix(*args) ⇒ Object

Optional prefix for lock keys; nil uses the default (see Distributed::KeyBuilder).



222
223
224
225
226
227
# File 'lib/race_guard/configuration.rb', line 222

def distributed_key_prefix(*args)
  return @mutex.synchronize { @distributed_key_prefix } if args.empty?

  @mutex.synchronize { @distributed_key_prefix = args.first }
  self
end

#distributed_lock_store(*args) ⇒ Object

— Epic 10: distributed execution guard (Redis-backed lock store) —



180
181
182
183
184
185
# File 'lib/race_guard/configuration.rb', line 180

def distributed_lock_store(*args)
  return @mutex.synchronize { @distributed_lock_store } if args.empty?

  @mutex.synchronize { @distributed_lock_store = args.first }
  self
end

#distributed_redis_client(*args) ⇒ Object



187
188
189
190
191
192
# File 'lib/race_guard/configuration.rb', line 187

def distributed_redis_client(*args)
  return @mutex.synchronize { @distributed_redis_client } if args.empty?

  @mutex.synchronize { @distributed_redis_client = args.first }
  self
end

#distributed_reentrancy(*args) ⇒ Object

When :skip, nested distributed_once on the same thread/key skips the inner block.



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/race_guard/configuration.rb', line 209

def distributed_reentrancy(*args)
  return @mutex.synchronize { @distributed_reentrancy } if args.empty?

  sym = args.first.to_sym
  unless sym == :skip
    raise ArgumentError, "invalid distributed_reentrancy: #{args.first.inspect}"
  end

  @mutex.synchronize { @distributed_reentrancy = sym }
  self
end

#distributed_skip_behavior(*args) ⇒ Object

Return value when the lock is not acquired: :nil (Ruby nil), :sentinel (Distributed::SKIPPED), or :raise (Distributed::LockNotAcquiredError).



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/race_guard/configuration.rb', line 196

def distributed_skip_behavior(*args)
  return @mutex.synchronize { @distributed_skip_behavior } if args.empty?

  sym = args.first.to_sym
  unless %i[nil sentinel raise].include?(sym)
    raise ArgumentError, "invalid distributed_skip_behavior: #{args.first.inspect}"
  end

  @mutex.synchronize { @distributed_skip_behavior = sym }
  self
end

#enable(name) ⇒ Object

rubocop:enable Metrics/MethodLength



35
36
37
38
39
# File 'lib/race_guard/configuration.rb', line 35

def enable(name)
  sym = name.to_sym
  @mutex.synchronize { @enabled.add(sym) }
  self
end

#enable_rule(name) ⇒ Object



56
57
58
59
60
# File 'lib/race_guard/configuration.rb', line 56

def enable_rule(name)
  sym = name.to_sym
  @mutex.synchronize { @enabled_rules.add(sym) }
  self
end

#enabled?(name) ⇒ Boolean

Returns:

  • (Boolean)


47
48
49
50
51
52
53
54
# File 'lib/race_guard/configuration.rb', line 47

def enabled?(name)
  sym = name.to_sym
  @mutex.synchronize do
    return false unless @environments.include?(current_environment)

    @enabled.include?(sym)
  end
end

#enabled_rule?(name) ⇒ Boolean

Returns:

  • (Boolean)


68
69
70
71
72
73
74
75
# File 'lib/race_guard/configuration.rb', line 68

def enabled_rule?(name)
  sym = name.to_sym
  @mutex.synchronize do
    return false unless @environments.include?(current_environment)

    @enabled_rules.include?(sym)
  end
end

#environments(*names) ⇒ Object



87
88
89
90
91
92
# File 'lib/race_guard/configuration.rb', line 87

def environments(*names)
  return @mutex.synchronize { @environments.dup } if names.empty?

  @mutex.synchronize { @environments = names.map(&:to_sym).freeze }
  self
end

#protect_detectorsObject



132
133
134
# File 'lib/race_guard/configuration.rb', line 132

def protect_detectors
  @mutex.synchronize { @protect_detectors.dup }
end

#remove_protect_detector(detector) ⇒ Object



122
123
124
125
# File 'lib/race_guard/configuration.rb', line 122

def remove_protect_detector(detector)
  @mutex.synchronize { @protect_detectors.delete(detector) }
  self
end

#remove_reporter(reporter) ⇒ Object



103
104
105
106
# File 'lib/race_guard/configuration.rb', line 103

def remove_reporter(reporter)
  @mutex.synchronize { @reporters.delete(reporter) }
  self
end

#reportersObject



113
114
115
# File 'lib/race_guard/configuration.rb', line 113

def reporters
  @mutex.synchronize { @reporters.dup }
end

#severity(*args) ⇒ Object



77
78
79
80
# File 'lib/race_guard/configuration.rb', line 77

def severity(*args)
  @mutex.synchronize { apply_severity_args(args) }
  self
end

#severity_for(name) ⇒ Object



82
83
84
85
# File 'lib/race_guard/configuration.rb', line 82

def severity_for(name)
  sym = name.to_sym
  @mutex.synchronize { @severities[sym] || @default_severity }
end

#shared_state_memo_globs(*patterns) ⇒ Object

Glob patterns (e.g. lib/*/.rb) scanned for @ivar ||= memoization (Epic 6.4). Empty by default: memo reports are disabled until patterns are set.



170
171
172
173
174
175
176
# File 'lib/race_guard/configuration.rb', line 170

def shared_state_memo_globs(*patterns)
  return @mutex.synchronize { @shared_state_memo_globs.dup } if patterns.empty?

  flat = patterns.length == 1 && patterns.first.is_a?(Array) ? patterns.first : patterns
  @mutex.synchronize { @shared_state_memo_globs = flat.compact.map(&:to_s).freeze }
  self
end

#to_hObject



238
239
240
# File 'lib/race_guard/configuration.rb', line 238

def to_h
  @mutex.synchronize { to_h_unsafe }
end

#watch_commit_safety(name, &block) ⇒ Object

Raises:

  • (ArgumentError)


136
137
138
139
140
141
142
143
144
145
# File 'lib/race_guard/configuration.rb', line 136

def watch_commit_safety(name, &block)
  raise ArgumentError, 'watch_commit_safety requires a block' unless block

  sym = name.to_sym
  @mutex.synchronize do
    dsl = CommitSafety::WatcherDSL.new(sym)
    block.call(dsl)
  end
  self
end