Class: Kaal::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/kaal/config/configuration.rb,
sig/kaal/config/configuration.rbs

Overview

Configuration class for Kaal Holds all settings for the scheduler, tick intervals, locks, and more.

Examples:

Basic configuration

Kaal.configure do |config|
  config.tick_interval = 5
  config.backend_config = { url: ENV['KAAL_BACKEND_URL'] }
  config.backend = :redis
end

Constant Summary collapse

DEFAULTS =

Default values for all configuration options

Returns:

  • ({ tick_interval: 5, window_lookback: 120, window_lookahead: 0, lease_ttl: 125, namespace: "kaal", backend: nil, backend_config: ::Hash[Kaal::rbs_any, Kaal::rbs_any], logger: nil, time_zone: nil, enable_log_dispatch_registry: false, enable_dispatch_recovery: true, recovery_window: 86400, recovery_startup_jitter: 5, scheduler_config_path: "config/kaal-scheduler.yml", scheduler_conflict_policy: :error, scheduler_missing_file_policy: :warn, delayed_job_allowed_class_prefixes: ::Array[Kaal::rbs_any] })
{
  tick_interval: 5,
  window_lookback: 120,
  window_lookahead: 0,
  lease_ttl: 125, # Must be >= window_lookback + tick_interval (120 + 5 = 125)
  namespace: 'kaal',
  backend: nil,
  backend_config: {},
  logger: nil,
  time_zone: nil,
  enable_log_dispatch_registry: false,
  enable_dispatch_recovery: true,
  recovery_window: 86_400, # 24 hours in seconds
  recovery_startup_jitter: 5, # max random delay in seconds
  scheduler_config_path: 'config/kaal-scheduler.yml',
  scheduler_conflict_policy: :error,
  scheduler_missing_file_policy: :warn,
  delayed_job_allowed_class_prefixes: []
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize ⇒ Configuration

Initialize a new Configuration instance with default values.



44
45
46
47
48
# File 'lib/kaal/config/configuration.rb', line 44

def initialize
  @values = DEFAULTS.dup
  @backend_name = nil
  @backend_runtime_context = nil
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args) ⇒ Kaal::rbs_any

Retrieve or assign configuration values by method name.

Parameters:

  • method_name (Kaal::rbs_any)
  • args (Kaal::rbs_any)

Returns:

  • (Kaal::rbs_any)


52
53
54
55
56
57
58
59
60
# File 'lib/kaal/config/configuration.rb', line 52

def method_missing(method_name, *args)
  handled, value = handle_known_key(method_name) do |key, setter|
    setter ? set_value(key, args.first) : @values[key]
  end

  return value if handled

  super
end

Instance Attribute Details

#backend_runtime_context ⇒ Kaal::rbs_any

Returns the value of attribute backend_runtime_context.

Returns:

  • (Kaal::rbs_any)


19
20
21
# File 'sig/kaal/config/configuration.rbs', line 19

def backend_runtime_context
  @backend_runtime_context
end

Instance Method Details

#add_backend_config_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


183
184
185
186
187
# File 'lib/kaal/config/configuration.rb', line 183

def add_backend_config_error(errors)
  return if @values[:backend_config].is_a?(Hash)

  errors << 'backend_config must be a hash'
end

#add_delayed_job_security_warning(warnings) ⇒ nil, Kaal::rbs_any

Parameters:

  • warnings (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


221
222
223
224
225
226
# File 'lib/kaal/config/configuration.rb', line 221

def add_delayed_job_security_warning(warnings)
  warning = Kaal::Config::DelayedJobSecurityPolicy.warning_for(self)
  return unless warning

  warnings << warning
end

#add_lease_ttl_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


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

def add_lease_ttl_error(errors)
  value = @values[:lease_ttl]
  return unless value.to_i <= 0

  errors << "lease_ttl must be greater than 0, got: #{value}"
end

#add_lease_ttl_window_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/kaal/config/configuration.rb', line 189

def add_lease_ttl_window_error(errors)
  lease_ttl = @values[:lease_ttl].to_i
  window_lookback = @values[:window_lookback].to_i
  tick_interval = @values[:tick_interval].to_i

  # Skip if individual validations already failed
  return if lease_ttl <= 0 || window_lookback.negative? || tick_interval <= 0

  minimum_ttl = window_lookback + tick_interval
  return unless lease_ttl < minimum_ttl

  errors << "lease_ttl (#{lease_ttl}s) must be >= window_lookback + tick_interval (#{minimum_ttl}s) to prevent duplicate dispatch"
end

#add_namespace_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


177
178
179
180
181
# File 'lib/kaal/config/configuration.rb', line 177

def add_namespace_error(errors)
  return unless @values[:namespace].to_s.strip.empty?

  errors << 'namespace cannot be blank'
end

#add_scheduler_config_path_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


203
204
205
206
207
# File 'lib/kaal/config/configuration.rb', line 203

def add_scheduler_config_path_error(errors)
  return unless @values[:scheduler_config_path].to_s.strip.empty?

  errors << 'scheduler_config_path cannot be blank'
end

#add_scheduler_conflict_policy_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


209
210
211
212
213
# File 'lib/kaal/config/configuration.rb', line 209

def add_scheduler_conflict_policy_error(errors)
  return if %i[error code_wins file_wins].include?(@values[:scheduler_conflict_policy])

  errors << 'scheduler_conflict_policy must be :error, :code_wins, or :file_wins'
end

#add_scheduler_missing_file_policy_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


215
216
217
218
219
# File 'lib/kaal/config/configuration.rb', line 215

def add_scheduler_missing_file_policy_error(errors)
  return if %i[warn error].include?(@values[:scheduler_missing_file_policy])

  errors << 'scheduler_missing_file_policy must be :warn or :error'
end

#add_tick_interval_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


149
150
151
152
153
154
# File 'lib/kaal/config/configuration.rb', line 149

def add_tick_interval_error(errors)
  value = @values[:tick_interval]
  return unless value.to_i <= 0

  errors << "tick_interval must be greater than 0, got: #{value}"
end

#add_window_lookahead_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


163
164
165
166
167
168
# File 'lib/kaal/config/configuration.rb', line 163

def add_window_lookahead_error(errors)
  value = @values[:window_lookahead]
  return unless value.to_i.negative?

  errors << "window_lookahead must be greater than or equal to 0, got: #{value}"
end

#add_window_lookback_error(errors) ⇒ nil, Kaal::rbs_any

Parameters:

  • errors (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


156
157
158
159
160
161
# File 'lib/kaal/config/configuration.rb', line 156

def add_window_lookback_error(errors)
  value = @values[:window_lookback]
  return unless value.to_i.negative?

  errors << "window_lookback must be greater than or equal to 0, got: #{value}"
end

#apply_backend_runtime_context(runtime_context) ⇒ Object



282
283
284
# File 'lib/kaal/config/configuration.rb', line 282

def apply_backend_runtime_context(runtime_context)
  @backend_runtime_context = runtime_context
end

#handle_known_key(method_name) {|arg0, arg1| ... } ⇒ ::Array[false | nil], ::Array[true | Kaal::rbs_any]

Parameters:

  • method_name (Kaal::rbs_any)

Yields:

Yield Parameters:

  • arg0 (Kaal::rbs_any)
  • arg1 (Kaal::rbs_any)

Yield Returns:

  • (Kaal::rbs_any)

Returns:

  • (::Array[false | nil], ::Array[true | Kaal::rbs_any])


228
229
230
231
232
233
234
235
# File 'lib/kaal/config/configuration.rb', line 228

def handle_known_key(method_name)
  name = method_name.to_s
  setter = name.end_with?('=')
  key = setter ? name.delete_suffix('=').to_sym : method_name.to_sym
  return [false, nil] unless @values.key?(key)

  [true, yield(key, setter)]
end

#normalize_backend(value) ⇒ Kaal::rbs_any

Parameters:

  • value (Kaal::rbs_any)

Returns:

  • (Kaal::rbs_any)


265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/kaal/config/configuration.rb', line 265

def normalize_backend(value)
  unless value.is_a?(String) || value.is_a?(Symbol)
    @backend_name = nil
    return value
  end

  normalized_backend_name = Kaal::Config::BackendFactory.normalize_name(value)
  backend = Kaal::Config::BackendFactory.build(
    normalized_backend_name,
    backend_config: @values[:backend_config],
    namespace: @values[:namespace],
    runtime_context: @backend_runtime_context
  )
  @backend_name = normalized_backend_name
  backend
end

#normalize_backend_config ⇒ Kaal::rbs_any

Parameters:

  • value (Kaal::rbs_any)

Returns:

  • (Kaal::rbs_any)


61
# File 'sig/kaal/config/configuration.rbs', line 61

def normalize_backend_config: (Kaal::rbs_any value) -> Kaal::rbs_any

#normalize_delayed_job_allowed_class_prefixes(value) ⇒ Kaal::rbs_any

Parameters:

  • value (Kaal::rbs_any)

Returns:

  • (Kaal::rbs_any)


300
301
302
303
304
305
# File 'lib/kaal/config/configuration.rb', line 300

def normalize_delayed_job_allowed_class_prefixes(value)
  Array(value).filter_map do |entry|
    normalized_entry = entry.to_s.strip
    normalized_entry unless normalized_entry.empty?
  end
end

#normalize_value(key, value) ⇒ Kaal::rbs_any

Parameters:

  • key (Kaal::rbs_any)
  • value (Kaal::rbs_any)

Returns:

  • (Kaal::rbs_any)


242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/kaal/config/configuration.rb', line 242

def normalize_value(key, value)
  case key
  when :backend
    normalize_backend(value)
  when :backend_config
    value.is_a?(Hash) ? Kaal::Support::HashTools.symbolize_keys(Kaal::Support::HashTools.deep_dup(value)) : (value || {})
  when :tick_interval, :window_lookback, :window_lookahead, :lease_ttl
    value.to_i
  when :namespace, :scheduler_config_path
    value.to_s
  when :time_zone
    normalize_optional_string(value)
  when :enable_log_dispatch_registry
    !!value
  when :scheduler_conflict_policy, :scheduler_missing_file_policy
    normalize_optional_symbol(value)
  when :delayed_job_allowed_class_prefixes
    normalize_delayed_job_allowed_class_prefixes(value)
  else
    value
  end
end

#rebuild_symbolic_backend_if_needed(key) ⇒ nil, Kaal::rbs_any

Parameters:

  • key (Kaal::rbs_any)

Returns:

  • (nil, Kaal::rbs_any)


287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/kaal/config/configuration.rb', line 287

def rebuild_symbolic_backend_if_needed(key)
  return unless @backend_name
  return unless %i[backend_config namespace backend].include?(key)
  return if key == :backend

  @values[:backend] = Kaal::Config::BackendFactory.build(
    @backend_name,
    backend_config: @values[:backend_config],
    namespace: @values[:namespace],
    runtime_context: @backend_runtime_context
  )
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Advertise supported configuration keys for respond_to?.

Parameters:

  • method_name (Kaal::rbs_any)
  • include_private (Boolean) (defaults to: false)

Returns:

  • (Boolean)


64
65
66
67
# File 'lib/kaal/config/configuration.rb', line 64

def respond_to_missing?(method_name, include_private = false)
  handled, value = handle_known_key(method_name) { true }
  (handled && value) || super
end

#set_value(key, value) ⇒ Kaal::rbs_any

Parameters:

  • key (Kaal::rbs_any)
  • value (Kaal::rbs_any)

Returns:

  • (Kaal::rbs_any)


237
238
239
240
# File 'lib/kaal/config/configuration.rb', line 237

def set_value(key, value)
  @values[key] = normalize_value(key, value)
  rebuild_symbolic_backend_if_needed(key)
end

#to_h ⇒ Hash

Get a hash representation of the current configuration.

Returns:

  • (Hash) —

    configuration as a hash



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/kaal/config/configuration.rb', line 107

def to_h
  backend = @values[:backend]
  logger = @values[:logger]

  {
    tick_interval: @values[:tick_interval],
    window_lookback: @values[:window_lookback],
    window_lookahead: @values[:window_lookahead],
    lease_ttl: @values[:lease_ttl],
    namespace: @values[:namespace],
    backend: backend&.class&.name,
    backend_config: Kaal::Support::HashTools.deep_dup(@values[:backend_config]),
    logger: logger&.class&.name,
    time_zone: @values[:time_zone],
    enable_log_dispatch_registry: @values[:enable_log_dispatch_registry],
    enable_dispatch_recovery: @values[:enable_dispatch_recovery],
    recovery_window: @values[:recovery_window],
    recovery_startup_jitter: @values[:recovery_startup_jitter],
    scheduler_config_path: @values[:scheduler_config_path],
    scheduler_conflict_policy: @values[:scheduler_conflict_policy],
    scheduler_missing_file_policy: @values[:scheduler_missing_file_policy],
    delayed_job_allowed_class_prefixes: @values[:delayed_job_allowed_class_prefixes]
  }
end

#validate ⇒ Array<String>

Validate configuration without raising.

Returns:

  • (Array<String>) —

    validation error messages



73
74
75
# File 'lib/kaal/config/configuration.rb', line 73

def validate
  validation_errors
end

#validate! ⇒ Configuration

Validate the configuration settings. Raises errors if required settings are invalid.

Returns:

Raises:



92
93
94
95
96
97
98
99
100
101
# File 'lib/kaal/config/configuration.rb', line 92

def validate!
  errors = validation_errors
  raise ConfigurationError, errors.join('; ') if errors.any?

  validation_warnings.each do |warning|
    @values[:logger]&.warn(warning)
  end

  self
end

#validation_errors ⇒ Kaal::rbs_any

Returns:

  • (Kaal::rbs_any)


134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/kaal/config/configuration.rb', line 134

def validation_errors
  errors = []
  add_tick_interval_error(errors)
  add_window_lookback_error(errors)
  add_window_lookahead_error(errors)
  add_lease_ttl_error(errors)
  add_namespace_error(errors)
  add_backend_config_error(errors)
  add_lease_ttl_window_error(errors)
  add_scheduler_config_path_error(errors)
  add_scheduler_conflict_policy_error(errors)
  add_scheduler_missing_file_policy_error(errors)
  errors
end

#validation_warnings ⇒ Array<String>

Non-fatal configuration warnings.

Returns:

  • (Array<String>) —

    warning messages



80
81
82
83
84
# File 'lib/kaal/config/configuration.rb', line 80

def validation_warnings
  warnings = []
  add_delayed_job_security_warning(warnings)
  warnings
end