kaal 0.2.1 → 0.3.0
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/README.md +81 -286
- data/Rakefile +4 -2
- data/config/kaal.rb +15 -0
- data/config/scheduler.yml +12 -0
- data/{lib/tasks/kaal_tasks.rake → exe/kaal} +5 -3
- data/lib/kaal/backend/adapter.rb +0 -1
- data/lib/kaal/backend/dispatch_attempt_logger.rb +33 -0
- data/lib/kaal/backend/dispatch_logging.rb +36 -23
- data/lib/kaal/backend/dispatch_registry_accessor.rb +43 -0
- data/lib/kaal/backend/memory_adapter.rb +7 -5
- data/lib/kaal/backend/redis_adapter.rb +6 -6
- data/lib/kaal/cli.rb +230 -0
- data/lib/kaal/{configuration.rb → config/configuration.rb} +0 -1
- data/lib/kaal/{scheduler_config_error.rb → config/scheduler_config_error.rb} +0 -1
- data/lib/kaal/config/scheduler_time_zone_resolver.rb +50 -0
- data/lib/kaal/config.rb +19 -0
- data/lib/kaal/{coordinator.rb → core/coordinator.rb} +42 -62
- data/lib/kaal/core/enabled_entry_enumerator.rb +51 -0
- data/lib/kaal/core/occurrence_finder.rb +38 -0
- data/lib/kaal/core.rb +18 -0
- data/lib/kaal/definition/memory_engine.rb +11 -18
- data/lib/kaal/definition/persistence_helpers.rb +31 -0
- data/lib/kaal/definition/redis_engine.rb +9 -6
- data/lib/kaal/definition/registry.rb +24 -2
- data/lib/kaal/definitions/registration_service.rb +62 -0
- data/lib/kaal/definitions/registry_accessor.rb +33 -0
- data/lib/kaal/dispatch/memory_engine.rb +3 -4
- data/lib/kaal/dispatch/redis_engine.rb +2 -3
- data/lib/kaal/dispatch/registry.rb +0 -1
- data/lib/kaal/register_conflict_support.rb +0 -1
- data/lib/kaal/registry.rb +0 -1
- data/lib/kaal/runtime/runtime_context.rb +41 -0
- data/lib/kaal/runtime/scheduler_boot_loader.rb +52 -0
- data/lib/kaal/runtime/signal_handler_chain.rb +42 -0
- data/lib/kaal/runtime/signal_handler_installer.rb +39 -0
- data/lib/kaal/runtime.rb +20 -0
- data/lib/kaal/scheduler_file/hash_transform.rb +22 -0
- data/lib/kaal/scheduler_file/helper_bundle.rb +28 -0
- data/lib/kaal/scheduler_file/job_applier.rb +242 -0
- data/lib/kaal/scheduler_file/job_normalizer.rb +90 -0
- data/lib/kaal/scheduler_file/loader.rb +152 -0
- data/lib/kaal/scheduler_file/payload_loader.rb +95 -0
- data/lib/kaal/{scheduler_placeholder_support.rb → scheduler_file/placeholder_support.rb} +0 -1
- data/lib/kaal/scheduler_file.rb +18 -0
- data/lib/kaal/support/hash_tools.rb +93 -0
- data/lib/kaal/{cron_humanizer.rb → utils/cron_humanizer.rb} +19 -1
- data/lib/kaal/{cron_utils.rb → utils/cron_utils.rb} +0 -1
- data/lib/kaal/{idempotency_key_generator.rb → utils/idempotency_key_generator.rb} +3 -3
- data/lib/kaal/utils.rb +18 -0
- data/lib/kaal/version.rb +1 -2
- data/lib/kaal.rb +77 -397
- metadata +64 -44
- data/app/models/kaal/cron_definition.rb +0 -76
- data/app/models/kaal/cron_dispatch.rb +0 -50
- data/app/models/kaal/cron_lock.rb +0 -38
- data/lib/generators/kaal/install/install_generator.rb +0 -72
- data/lib/generators/kaal/install/templates/create_kaal_definitions.rb.tt +0 -21
- data/lib/generators/kaal/install/templates/create_kaal_dispatches.rb.tt +0 -20
- data/lib/generators/kaal/install/templates/create_kaal_locks.rb.tt +0 -17
- data/lib/generators/kaal/install/templates/kaal.rb.tt +0 -31
- data/lib/generators/kaal/install/templates/scheduler.yml.tt +0 -22
- data/lib/kaal/backend/mysql_adapter.rb +0 -170
- data/lib/kaal/backend/postgres_adapter.rb +0 -134
- data/lib/kaal/backend/sqlite_adapter.rb +0 -116
- data/lib/kaal/definition/database_engine.rb +0 -50
- data/lib/kaal/dispatch/database_engine.rb +0 -94
- data/lib/kaal/railtie.rb +0 -183
- data/lib/kaal/rake_tasks.rb +0 -184
- data/lib/kaal/scheduler_file_loader.rb +0 -321
- data/lib/kaal/scheduler_hash_transform.rb +0 -45
data/lib/kaal.rb
CHANGED
|
@@ -4,224 +4,94 @@
|
|
|
4
4
|
#
|
|
5
5
|
# This source code is licensed under the MIT license found in the
|
|
6
6
|
# LICENSE file in the root directory of this source tree.
|
|
7
|
-
|
|
8
7
|
require 'kaal/version'
|
|
9
|
-
require 'kaal/
|
|
8
|
+
require 'kaal/config'
|
|
10
9
|
require 'kaal/registry'
|
|
11
10
|
require 'kaal/dispatch/registry'
|
|
12
11
|
require 'kaal/dispatch/memory_engine'
|
|
13
12
|
require 'kaal/dispatch/redis_engine'
|
|
14
|
-
require 'kaal/dispatch/database_engine'
|
|
15
13
|
require 'kaal/definition/registry'
|
|
16
14
|
require 'kaal/definition/memory_engine'
|
|
17
15
|
require 'kaal/definition/redis_engine'
|
|
18
|
-
require 'kaal/definition/database_engine'
|
|
19
16
|
require 'kaal/backend/adapter'
|
|
20
17
|
require 'kaal/backend/memory_adapter'
|
|
21
18
|
require 'kaal/backend/redis_adapter'
|
|
22
|
-
require 'kaal/backend/
|
|
23
|
-
require 'kaal/backend/
|
|
24
|
-
require 'kaal/
|
|
25
|
-
require 'kaal/idempotency_key_generator'
|
|
26
|
-
require 'kaal/cron_utils'
|
|
27
|
-
require 'kaal/cron_humanizer'
|
|
28
|
-
require 'kaal/scheduler_config_error'
|
|
29
|
-
require 'kaal/scheduler_file_loader'
|
|
19
|
+
require 'kaal/backend/dispatch_registry_accessor'
|
|
20
|
+
require 'kaal/backend/dispatch_attempt_logger'
|
|
21
|
+
require 'kaal/utils'
|
|
30
22
|
require 'kaal/register_conflict_support'
|
|
31
|
-
require 'kaal/
|
|
32
|
-
require 'kaal/
|
|
33
|
-
require 'kaal/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
# @example Configure Kaal
|
|
40
|
-
# Kaal.configure do |config|
|
|
41
|
-
# config.tick_interval = 5
|
|
42
|
-
# config.backend = Kaal::Backend::RedisAdapter.new(Redis.new(url: ENV["REDIS_URL"]))
|
|
43
|
-
# end
|
|
44
|
-
#
|
|
45
|
-
# @example Register a cron job
|
|
46
|
-
# Kaal.register(
|
|
47
|
-
# key: "reports:daily",
|
|
48
|
-
# cron: "0 9 * * *",
|
|
49
|
-
# enqueue: ->(fire_time:, idempotency_key:) { MyJob.perform_later }
|
|
50
|
-
# )
|
|
23
|
+
require 'kaal/definitions/registry_accessor'
|
|
24
|
+
require 'kaal/definitions/registration_service'
|
|
25
|
+
require 'kaal/runtime'
|
|
26
|
+
require 'kaal/scheduler_file'
|
|
27
|
+
require 'kaal/core'
|
|
28
|
+
require 'kaal/support/hash_tools'
|
|
29
|
+
|
|
30
|
+
# Plain-Ruby scheduler surface with configurable backends, registries, and CLI helpers.
|
|
51
31
|
module Kaal
|
|
52
32
|
class << self
|
|
53
33
|
include RegisterConflictSupport
|
|
54
34
|
|
|
55
|
-
##
|
|
56
|
-
# Get the current configuration instance.
|
|
57
|
-
#
|
|
58
|
-
# @return [Configuration] the global configuration object
|
|
59
35
|
def configuration
|
|
60
36
|
@configuration ||= Configuration.new
|
|
61
37
|
end
|
|
62
38
|
|
|
63
|
-
##
|
|
64
|
-
# Get the current registry instance.
|
|
65
|
-
#
|
|
66
|
-
# @return [Registry] the global registry object
|
|
67
39
|
def registry
|
|
68
40
|
@registry ||= Registry.new
|
|
69
41
|
end
|
|
70
42
|
|
|
71
|
-
##
|
|
72
|
-
# Get the coordinator instance.
|
|
73
|
-
#
|
|
74
|
-
# @return [Coordinator] the global coordinator object
|
|
75
43
|
def coordinator
|
|
76
44
|
@coordinator ||= Coordinator.new(configuration: configuration, registry: registry)
|
|
77
45
|
end
|
|
78
46
|
|
|
79
|
-
##
|
|
80
|
-
# Reset configuration to defaults. Primarily used in tests.
|
|
81
|
-
#
|
|
82
|
-
# @return [Configuration] a fresh configuration object
|
|
83
47
|
def reset_configuration!
|
|
84
48
|
@configuration = Configuration.new
|
|
85
|
-
@coordinator = nil
|
|
49
|
+
@coordinator = nil
|
|
86
50
|
@definition_registry = nil
|
|
51
|
+
@definitions_registry_accessor = nil
|
|
52
|
+
@dispatch_registry_accessor = nil
|
|
87
53
|
end
|
|
88
54
|
|
|
89
|
-
##
|
|
90
|
-
# Reset registry to empty state. Primarily used in tests.
|
|
91
|
-
#
|
|
92
|
-
# @return [Registry] a fresh registry object
|
|
93
55
|
def reset_registry!
|
|
94
56
|
@registry = Registry.new
|
|
95
|
-
@definition_registry
|
|
96
|
-
|
|
57
|
+
definition_registry = @definition_registry
|
|
58
|
+
definition_registry.clear if definition_registry.respond_to?(:clear)
|
|
59
|
+
@coordinator = nil
|
|
97
60
|
end
|
|
98
61
|
|
|
99
|
-
##
|
|
100
|
-
# Reset coordinator to initial state. Primarily used in tests.
|
|
101
|
-
#
|
|
102
|
-
# Stops any running coordinator and creates a fresh instance.
|
|
103
|
-
#
|
|
104
|
-
# @return [Coordinator] a fresh coordinator object
|
|
105
|
-
# @raise [RuntimeError] if the running coordinator cannot be stopped within timeout
|
|
106
62
|
def reset_coordinator!
|
|
107
|
-
# Stop the existing coordinator if it's running
|
|
108
63
|
if @coordinator&.running?
|
|
109
64
|
stopped = @coordinator.stop!
|
|
110
65
|
raise 'Failed to stop coordinator thread within timeout' unless stopped
|
|
111
66
|
end
|
|
112
67
|
|
|
113
|
-
# Create and return a fresh coordinator
|
|
114
68
|
@coordinator = nil
|
|
115
69
|
coordinator
|
|
116
70
|
end
|
|
117
71
|
|
|
118
|
-
##
|
|
119
|
-
# Configure Kaal via a block.
|
|
120
|
-
#
|
|
121
|
-
# @yield [config] yields the configuration object
|
|
122
|
-
# @yieldparam config [Configuration] the configuration instance to customize
|
|
123
|
-
# @return [void]
|
|
124
|
-
#
|
|
125
|
-
# @example
|
|
126
|
-
# Kaal.configure do |config|
|
|
127
|
-
# config.tick_interval = 10
|
|
128
|
-
# config.lease_ttl = 120
|
|
129
|
-
# end
|
|
130
72
|
def configure
|
|
131
73
|
yield(configuration) if block_given?
|
|
132
74
|
end
|
|
133
75
|
|
|
134
|
-
##
|
|
135
|
-
# Register a new cron job.
|
|
136
|
-
#
|
|
137
|
-
# @param key [String] unique identifier for the cron task
|
|
138
|
-
# @param cron [String] cron expression (e.g., "0 9 * * *", "@daily")
|
|
139
|
-
# @param enqueue [Proc, Lambda] callable executed when cron fires
|
|
140
|
-
# @return [Registry::Entry] the registered entry
|
|
141
|
-
#
|
|
142
|
-
# @raise [ArgumentError] if parameters are invalid
|
|
143
|
-
# @raise [RegistryError] if key is already registered
|
|
144
|
-
#
|
|
145
|
-
# @example
|
|
146
|
-
# Kaal.register(
|
|
147
|
-
# key: "reports:weekly_summary",
|
|
148
|
-
# cron: "0 9 * * 1",
|
|
149
|
-
# enqueue: ->(fire_time:, idempotency_key:) { WeeklySummaryJob.perform_later }
|
|
150
|
-
# )
|
|
151
76
|
def register(key:, cron:, enqueue:)
|
|
152
|
-
|
|
153
|
-
existing_entry = registry.find(key)
|
|
154
|
-
if existing_entry
|
|
155
|
-
conflict_result = resolve_register_conflict(
|
|
156
|
-
key: key,
|
|
157
|
-
cron: cron,
|
|
158
|
-
enqueue: enqueue,
|
|
159
|
-
existing_definition: existing_definition,
|
|
160
|
-
existing_entry: existing_entry
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
return conflict_result if conflict_result
|
|
164
|
-
|
|
165
|
-
raise RegistryError, "Key '#{key}' is already registered"
|
|
166
|
-
end
|
|
167
|
-
persisted_attributes = {
|
|
168
|
-
enabled: true,
|
|
169
|
-
source: 'code',
|
|
170
|
-
metadata: {}
|
|
171
|
-
}.merge(existing_definition&.slice(:enabled, :metadata) || {})
|
|
172
|
-
definition_registry.upsert_definition(key: key, cron: cron, **persisted_attributes)
|
|
173
|
-
with_registered_definition_rollback(key, existing_definition) do
|
|
174
|
-
registry.add(key: key, cron: cron, enqueue: enqueue)
|
|
175
|
-
end
|
|
77
|
+
registration_service.call(key:, cron:, enqueue:)
|
|
176
78
|
end
|
|
177
79
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
-
# @return [Array<Hash>] normalized jobs applied from scheduler file
|
|
182
|
-
# @raise [SchedulerConfigError] if scheduler file is invalid
|
|
183
|
-
def load_scheduler_file!
|
|
184
|
-
loader = SchedulerFileLoader.new(
|
|
80
|
+
def load_scheduler_file!(runtime_context: RuntimeContext.default)
|
|
81
|
+
SchedulerFileLoader.new(
|
|
185
82
|
configuration: configuration,
|
|
186
83
|
definition_registry: definition_registry,
|
|
187
84
|
registry: registry,
|
|
188
|
-
logger: configuration.logger
|
|
189
|
-
|
|
190
|
-
|
|
85
|
+
logger: configuration.logger,
|
|
86
|
+
runtime_context: runtime_context
|
|
87
|
+
).load
|
|
191
88
|
end
|
|
192
89
|
|
|
193
|
-
##
|
|
194
|
-
# Unregister (remove) a cron job by key.
|
|
195
|
-
#
|
|
196
|
-
# @param key [String] the unique identifier of the job to remove
|
|
197
|
-
# @return [Registry::Entry, nil] the removed entry, or nil if not found
|
|
198
90
|
def unregister(key:)
|
|
199
91
|
definition_registry.remove_definition(key)
|
|
200
92
|
registry.remove(key)
|
|
201
93
|
end
|
|
202
94
|
|
|
203
|
-
def rollback_registered_definition(key, existing_definition)
|
|
204
|
-
if existing_definition
|
|
205
|
-
definition_registry.upsert_definition(
|
|
206
|
-
key: existing_definition[:key],
|
|
207
|
-
cron: existing_definition[:cron],
|
|
208
|
-
enabled: existing_definition[:enabled],
|
|
209
|
-
source: existing_definition[:source],
|
|
210
|
-
metadata: existing_definition[:metadata]
|
|
211
|
-
)
|
|
212
|
-
elsif !registry.registered?(key)
|
|
213
|
-
definition_registry.remove_definition(key)
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
private :rollback_registered_definition
|
|
217
|
-
|
|
218
|
-
##
|
|
219
|
-
# Get all registered cron jobs.
|
|
220
|
-
#
|
|
221
|
-
# @return [Array<Registry::Entry>] array of all registered entries
|
|
222
|
-
#
|
|
223
|
-
# @example
|
|
224
|
-
# Kaal.registered.each { |entry| puts entry.key }
|
|
225
95
|
def registered
|
|
226
96
|
definition_registry.all_definitions.map do |definition|
|
|
227
97
|
key = definition[:key]
|
|
@@ -230,297 +100,97 @@ module Kaal
|
|
|
230
100
|
end
|
|
231
101
|
end
|
|
232
102
|
|
|
233
|
-
##
|
|
234
|
-
# Check if a cron job is registered by key.
|
|
235
|
-
#
|
|
236
|
-
# @param key [String] the unique identifier to check
|
|
237
|
-
# @return [Boolean] true if the key is registered, false otherwise
|
|
238
|
-
#
|
|
239
|
-
# @example
|
|
240
|
-
# Kaal.registered?(key: "reports:daily") # => true
|
|
241
103
|
def registered?(key:)
|
|
242
|
-
definition_registry.find_definition(key)
|
|
104
|
+
!!definition_registry.find_definition(key)
|
|
243
105
|
end
|
|
244
106
|
|
|
245
|
-
##
|
|
246
|
-
# Enable a registered cron definition by key.
|
|
247
|
-
#
|
|
248
|
-
# @param key [String] the unique identifier to enable
|
|
249
|
-
# @return [Hash, nil] enabled definition hash or nil if missing
|
|
250
107
|
def enable(key:)
|
|
251
108
|
definition_registry.enable_definition(key)
|
|
252
109
|
end
|
|
253
110
|
|
|
254
|
-
##
|
|
255
|
-
# Disable a registered cron definition by key.
|
|
256
|
-
#
|
|
257
|
-
# @param key [String] the unique identifier to disable
|
|
258
|
-
# @return [Hash, nil] disabled definition hash or nil if missing
|
|
259
111
|
def disable(key:)
|
|
260
112
|
definition_registry.disable_definition(key)
|
|
261
113
|
end
|
|
262
114
|
|
|
263
|
-
##
|
|
264
|
-
# Start the scheduler background thread.
|
|
265
|
-
#
|
|
266
|
-
# The coordinator will calculate due fire times for each registered cron
|
|
267
|
-
# on each tick and attempt to dispatch work.
|
|
268
|
-
#
|
|
269
|
-
# @return [Thread] the started thread, or nil if already running
|
|
270
|
-
#
|
|
271
|
-
# @example
|
|
272
|
-
# Kaal.start!
|
|
273
115
|
def start!
|
|
274
116
|
coordinator.start!
|
|
275
117
|
end
|
|
276
118
|
|
|
277
|
-
##
|
|
278
|
-
# Stop the scheduler gracefully.
|
|
279
|
-
#
|
|
280
|
-
# Signals the coordinator to stop after the current tick completes,
|
|
281
|
-
# then waits for the thread to finish.
|
|
282
|
-
#
|
|
283
|
-
# @param timeout [Integer] seconds to wait for graceful shutdown (default: 30)
|
|
284
|
-
# @return [Boolean] true if stopped successfully
|
|
285
|
-
#
|
|
286
|
-
# @example
|
|
287
|
-
# Kaal.stop!
|
|
288
|
-
# @example
|
|
289
|
-
# Kaal.stop!(timeout: 60)
|
|
290
119
|
def stop!(timeout: 30)
|
|
291
120
|
coordinator.stop!(timeout: timeout)
|
|
292
121
|
end
|
|
293
122
|
|
|
294
|
-
##
|
|
295
|
-
# Check if the scheduler is currently running.
|
|
296
|
-
#
|
|
297
|
-
# @return [Boolean] true if running, false otherwise
|
|
298
|
-
#
|
|
299
|
-
# @example
|
|
300
|
-
# if Kaal.running?
|
|
301
|
-
# puts "Scheduler is active"
|
|
302
|
-
# end
|
|
303
123
|
def running?
|
|
304
124
|
coordinator.running?
|
|
305
125
|
end
|
|
306
126
|
|
|
307
|
-
##
|
|
308
|
-
# Restart the scheduler (stop then start).
|
|
309
|
-
#
|
|
310
|
-
# @return [Thread] the started thread
|
|
311
|
-
#
|
|
312
|
-
# @example
|
|
313
|
-
# Kaal.restart!
|
|
314
127
|
def restart!
|
|
315
128
|
coordinator.restart!
|
|
316
129
|
end
|
|
317
130
|
|
|
318
|
-
##
|
|
319
|
-
# Execute a single scheduler tick manually.
|
|
320
|
-
#
|
|
321
|
-
# This is useful for testing and Rake tasks that want to trigger
|
|
322
|
-
# the scheduler without running the background loop.
|
|
323
|
-
#
|
|
324
|
-
# @return [void]
|
|
325
|
-
#
|
|
326
|
-
# @example
|
|
327
|
-
# Kaal.tick!
|
|
328
131
|
def tick!
|
|
329
132
|
coordinator.tick!
|
|
330
133
|
end
|
|
331
134
|
|
|
332
|
-
##
|
|
333
|
-
# Generate an idempotency key for a cron job and yield to a block.
|
|
334
|
-
#
|
|
335
|
-
# Useful for advanced use cases where you need to generate an idempotency key
|
|
336
|
-
# outside of the normal enqueue flow, or for internal utilities.
|
|
337
|
-
#
|
|
338
|
-
# @param key [String] the cron job key
|
|
339
|
-
# @param fire_time [Time] the fire time
|
|
340
|
-
# @yield [String] yields the generated idempotency key
|
|
341
|
-
# @return [Object] the result of the block
|
|
342
|
-
#
|
|
343
|
-
# @raise [ArgumentError] if no block is provided
|
|
344
|
-
#
|
|
345
|
-
# @example
|
|
346
|
-
# Kaal.with_idempotency('reports:daily', Time.current) do |idempotency_key|
|
|
347
|
-
# MyJob.perform_later(key: idempotency_key)
|
|
348
|
-
# end
|
|
349
135
|
def with_idempotency(key, fire_time)
|
|
350
136
|
raise ArgumentError, 'block required' unless block_given?
|
|
351
137
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
##
|
|
357
|
-
# Check if a cron job has already been dispatched for a given fire time.
|
|
358
|
-
#
|
|
359
|
-
# Useful for implementing deduplication logic to prevent duplicate job enqueues.
|
|
360
|
-
# Returns true if dispatch logging is enabled and the job was previously dispatched,
|
|
361
|
-
# returns false if not found or dispatch logging is disabled.
|
|
362
|
-
#
|
|
363
|
-
# Safe to call from enqueue callbacks - will return false on any error (e.g., backend
|
|
364
|
-
# misconfiguration or temporary failure), log via configuration.logger, and never raise.
|
|
365
|
-
#
|
|
366
|
-
# @param key [String] the cron job key
|
|
367
|
-
# @param fire_time [Time] the fire time to check
|
|
368
|
-
# @return [Boolean] true if dispatch exists, false otherwise (never raises)
|
|
369
|
-
#
|
|
370
|
-
# @example
|
|
371
|
-
# Kaal.dispatched?('reports:daily', Time.current)
|
|
372
|
-
# # => true if already dispatched, false otherwise
|
|
373
|
-
def dispatched?(key, fire_time)
|
|
374
|
-
adapter = configuration.backend
|
|
375
|
-
return false if adapter.nil? || !adapter.respond_to?(:dispatch_registry)
|
|
376
|
-
|
|
377
|
-
registry = adapter.dispatch_registry
|
|
378
|
-
return false if registry.nil?
|
|
379
|
-
|
|
380
|
-
registry.dispatched?(key, fire_time)
|
|
381
|
-
rescue StandardError => e
|
|
382
|
-
configuration.logger&.warn("Error checking dispatch status for #{key}: #{e.message}")
|
|
383
|
-
false
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
##
|
|
387
|
-
# Get the dispatch log registry for querying dispatch history.
|
|
388
|
-
#
|
|
389
|
-
# Returns the underlying dispatch registry engine which allows querying
|
|
390
|
-
# dispatch records. The specific methods available depend on the adapter type:
|
|
391
|
-
#
|
|
392
|
-
# **Common methods (all adapters):**
|
|
393
|
-
# - `find_dispatch(key, fire_time)` - Find a specific dispatch record
|
|
394
|
-
# - `dispatched?(key, fire_time)` - Check if a dispatch exists
|
|
395
|
-
#
|
|
396
|
-
# **Database adapter specific methods:**
|
|
397
|
-
# - `find_by_key(key)` - Find all dispatches for a job key
|
|
398
|
-
# - `find_by_node(node_id)` - Find all dispatches from a node, ordered by fire_time
|
|
399
|
-
# - `find_by_status(status)` - Find dispatches by status ('dispatched', 'failed', etc.)
|
|
400
|
-
# - `cleanup(recovery_window: 86400)` - Delete dispatch records older than window
|
|
401
|
-
#
|
|
402
|
-
# **Redis adapter:**
|
|
403
|
-
# - Uses automatic TTL expiration (no cleanup needed)
|
|
404
|
-
#
|
|
405
|
-
# **Memory adapter (development/testing):**
|
|
406
|
-
# - `clear()` - Clear all stored records
|
|
407
|
-
# - `size()` - Get count of stored records
|
|
408
|
-
#
|
|
409
|
-
# Safe for production diagnostics - will return nil on any error (e.g., backend
|
|
410
|
-
# misconfiguration or temporary failure), log via configuration.logger, and never raise.
|
|
411
|
-
#
|
|
412
|
-
# @return [Dispatch::Registry, nil] the dispatch registry instance, or nil if adapter doesn't support it or on error
|
|
413
|
-
#
|
|
414
|
-
# @example Query dispatches with database adapter
|
|
415
|
-
# registry = Kaal.dispatch_log_registry
|
|
416
|
-
# # Find all dispatches for a job
|
|
417
|
-
# registry.find_by_key('reports:daily')
|
|
418
|
-
# # Find failed attempts
|
|
419
|
-
# registry.find_by_status('failed')
|
|
420
|
-
# # Clean up old records (over 30 days old)
|
|
421
|
-
# registry.cleanup(recovery_window: 30 * 24 * 60 * 60)
|
|
422
|
-
#
|
|
423
|
-
# @example Query dispatches with memory adapter
|
|
424
|
-
# registry = Kaal.dispatch_log_registry
|
|
425
|
-
# record = registry.find_dispatch('reports:daily', Time.current)
|
|
426
|
-
# total = registry.size
|
|
427
|
-
# registry.clear
|
|
428
|
-
def dispatch_log_registry
|
|
429
|
-
adapter = configuration.backend
|
|
430
|
-
return nil if adapter.nil? || !adapter.respond_to?(:dispatch_registry)
|
|
431
|
-
|
|
432
|
-
registry = adapter.dispatch_registry
|
|
433
|
-
return nil if registry.nil?
|
|
138
|
+
yield(IdempotencyKeyGenerator.call(key, fire_time, configuration: configuration))
|
|
139
|
+
end
|
|
434
140
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
configuration.logger&.warn("Error accessing dispatch registry: #{e.message}")
|
|
438
|
-
nil
|
|
141
|
+
def dispatched?(key, fire_time)
|
|
142
|
+
dispatch_registry_accessor.dispatched?(key, fire_time)
|
|
439
143
|
end
|
|
440
144
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
def tick_interval
|
|
444
|
-
configuration.tick_interval
|
|
145
|
+
def dispatch_log_registry
|
|
146
|
+
dispatch_registry_accessor.registry
|
|
445
147
|
end
|
|
446
148
|
|
|
149
|
+
def tick_interval = configuration.tick_interval
|
|
150
|
+
def window_lookback = configuration.window_lookback
|
|
151
|
+
def window_lookahead = configuration.window_lookahead
|
|
152
|
+
def lease_ttl = configuration.lease_ttl
|
|
153
|
+
def namespace = configuration.namespace
|
|
154
|
+
def backend = configuration.backend
|
|
155
|
+
def logger = configuration.logger
|
|
156
|
+
def time_zone = configuration.time_zone
|
|
157
|
+
|
|
447
158
|
def tick_interval=(value)
|
|
448
159
|
configuration.tick_interval = value
|
|
449
160
|
end
|
|
450
161
|
|
|
451
|
-
def window_lookback
|
|
452
|
-
configuration.window_lookback
|
|
453
|
-
end
|
|
454
|
-
|
|
455
162
|
def window_lookback=(value)
|
|
456
163
|
configuration.window_lookback = value
|
|
457
164
|
end
|
|
458
165
|
|
|
459
|
-
def window_lookahead
|
|
460
|
-
configuration.window_lookahead
|
|
461
|
-
end
|
|
462
|
-
|
|
463
166
|
def window_lookahead=(value)
|
|
464
167
|
configuration.window_lookahead = value
|
|
465
168
|
end
|
|
466
169
|
|
|
467
|
-
def lease_ttl
|
|
468
|
-
configuration.lease_ttl
|
|
469
|
-
end
|
|
470
|
-
|
|
471
170
|
def lease_ttl=(value)
|
|
472
171
|
configuration.lease_ttl = value
|
|
473
172
|
end
|
|
474
173
|
|
|
475
|
-
def namespace
|
|
476
|
-
configuration.namespace
|
|
477
|
-
end
|
|
478
|
-
|
|
479
174
|
def namespace=(value)
|
|
480
175
|
configuration.namespace = value
|
|
481
176
|
end
|
|
482
177
|
|
|
483
|
-
def backend
|
|
484
|
-
configuration.backend
|
|
485
|
-
end
|
|
486
|
-
|
|
487
178
|
def backend=(value)
|
|
488
179
|
configuration.backend = value
|
|
489
180
|
end
|
|
490
181
|
|
|
491
|
-
##
|
|
492
|
-
# Definition registry access.
|
|
493
|
-
#
|
|
494
|
-
# Uses backend-provided definition registry when available, otherwise a
|
|
495
|
-
# process-local in-memory fallback.
|
|
496
|
-
#
|
|
497
|
-
# @return [Kaal::Definition::Registry]
|
|
498
|
-
def definition_registry
|
|
499
|
-
configured_backend = configuration.backend
|
|
500
|
-
registry = configured_backend&.definition_registry
|
|
501
|
-
return registry if registry
|
|
502
|
-
|
|
503
|
-
@definition_registry ||= Definition::MemoryEngine.new
|
|
504
|
-
rescue NoMethodError
|
|
505
|
-
@definition_registry ||= Definition::MemoryEngine.new
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
def logger
|
|
509
|
-
configuration.logger
|
|
510
|
-
end
|
|
511
|
-
|
|
512
182
|
def logger=(value)
|
|
513
183
|
configuration.logger = value
|
|
514
184
|
end
|
|
515
185
|
|
|
516
|
-
def time_zone
|
|
517
|
-
configuration.time_zone
|
|
518
|
-
end
|
|
519
|
-
|
|
520
186
|
def time_zone=(value)
|
|
521
187
|
configuration.time_zone = value
|
|
522
188
|
end
|
|
523
189
|
|
|
190
|
+
def definition_registry
|
|
191
|
+
definitions_registry_accessor.call
|
|
192
|
+
end
|
|
193
|
+
|
|
524
194
|
def validate
|
|
525
195
|
configuration.validate
|
|
526
196
|
end
|
|
@@ -529,43 +199,53 @@ module Kaal
|
|
|
529
199
|
configuration.validate!
|
|
530
200
|
end
|
|
531
201
|
|
|
532
|
-
##
|
|
533
|
-
# Validate a cron expression.
|
|
534
|
-
#
|
|
535
|
-
# @param expression [String] cron expression
|
|
536
|
-
# @return [Boolean] true if valid, false otherwise
|
|
537
202
|
def valid?(expression)
|
|
538
203
|
CronUtils.valid?(expression)
|
|
539
204
|
end
|
|
540
205
|
|
|
541
|
-
##
|
|
542
|
-
# Simplify a cron expression to a predefined macro when possible.
|
|
543
|
-
#
|
|
544
|
-
# @param expression [String] cron expression
|
|
545
|
-
# @return [String] simplified expression or canonical input
|
|
546
|
-
# @raise [ArgumentError] when expression is invalid
|
|
547
206
|
def simplify(expression)
|
|
548
207
|
CronUtils.simplify(expression)
|
|
549
208
|
end
|
|
550
209
|
|
|
551
|
-
##
|
|
552
|
-
# Lint a cron expression and return warnings/errors.
|
|
553
|
-
#
|
|
554
|
-
# @param expression [String] cron expression
|
|
555
|
-
# @return [Array<String>] lint warnings/errors
|
|
556
210
|
def lint(expression)
|
|
557
211
|
CronUtils.lint(expression)
|
|
558
212
|
end
|
|
559
213
|
|
|
560
|
-
##
|
|
561
|
-
# Convert a cron expression to a human-friendly phrase.
|
|
562
|
-
#
|
|
563
|
-
# @param expression [String] cron expression
|
|
564
|
-
# @param locale [Symbol, String, nil] locale override (defaults to current I18n.locale)
|
|
565
|
-
# @return [String] localized phrase
|
|
566
|
-
# @raise [ArgumentError] when expression is invalid
|
|
567
214
|
def to_human(expression, locale: nil)
|
|
568
215
|
CronHumanizer.to_human(expression, locale: locale)
|
|
569
216
|
end
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def rollback_registered_definition(key, existing_definition)
|
|
221
|
+
if existing_definition
|
|
222
|
+
definition_registry.upsert_definition(
|
|
223
|
+
**Definition::AttributeHelpers.definition_attributes(existing_definition), enabled: existing_definition[:enabled]
|
|
224
|
+
)
|
|
225
|
+
elsif !registry.registered?(key)
|
|
226
|
+
definition_registry.remove_definition(key)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def registration_service
|
|
231
|
+
@registration_service ||= Definitions::RegistrationService.new(
|
|
232
|
+
configuration: configuration,
|
|
233
|
+
definition_registry: definition_registry,
|
|
234
|
+
registry: registry
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def definitions_registry_accessor
|
|
239
|
+
@definitions_registry_accessor ||= Definitions::RegistryAccessor.new(
|
|
240
|
+
configuration: configuration,
|
|
241
|
+
fallback_registry_provider: lambda {
|
|
242
|
+
@definition_registry ||= Definition::MemoryEngine.new
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def dispatch_registry_accessor
|
|
248
|
+
@dispatch_registry_accessor ||= Backend::DispatchRegistryAccessor.new(configuration: configuration)
|
|
249
|
+
end
|
|
570
250
|
end
|
|
571
251
|
end
|