kaal 0.4.0 → 0.6.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 +60 -25
- data/config/kaal.yml +12 -0
- data/lib/kaal/active_record_support.rb +2 -2
- data/lib/kaal/backend/adapter.rb +8 -0
- data/lib/kaal/backend/memory_adapter.rb +5 -0
- data/lib/kaal/backend/mysql.rb +25 -3
- data/lib/kaal/backend/postgres.rb +6 -2
- data/lib/kaal/backend/redis_adapter.rb +5 -0
- data/lib/kaal/backend/sqlite.rb +4 -0
- data/lib/kaal/cli.rb +38 -33
- data/lib/kaal/config/backend_factory.rb +178 -0
- data/lib/kaal/config/configuration.rb +98 -9
- data/lib/kaal/config/delayed_job_security_policy.rb +60 -0
- data/lib/kaal/config/file_loader.rb +187 -0
- data/lib/kaal/config.rb +3 -0
- data/lib/kaal/core/coordinator.rb +68 -19
- data/lib/kaal/delayed_job/database_engine.rb +116 -0
- data/lib/kaal/delayed_job/dispatch_failure_logger.rb +31 -0
- data/lib/kaal/delayed_job/memory_engine.rb +79 -0
- data/lib/kaal/delayed_job/mysql_version_support.rb +43 -0
- data/lib/kaal/delayed_job/redis_engine.rb +119 -0
- data/lib/kaal/delayed_job/registry.rb +39 -0
- data/lib/kaal/internal/active_record/database_backend.rb +5 -0
- data/lib/kaal/internal/active_record/delayed_job_record.rb +16 -0
- data/lib/kaal/internal/active_record/delayed_job_registry.rb +119 -0
- data/lib/kaal/internal/active_record/migration_templates.rb +33 -3
- data/lib/kaal/internal/active_record/mysql_backend.rb +23 -5
- data/lib/kaal/internal/active_record/postgres_backend.rb +4 -0
- data/lib/kaal/internal/active_record.rb +2 -0
- data/lib/kaal/internal/sequel/database_backend.rb +5 -0
- data/lib/kaal/internal/sequel/mysql_backend.rb +15 -1
- data/lib/kaal/internal/sequel/postgres_backend.rb +4 -0
- data/lib/kaal/internal/sequel.rb +1 -0
- data/lib/kaal/job_dispatcher.rb +108 -0
- data/lib/kaal/persistence/database.rb +4 -0
- data/lib/kaal/persistence/migration_templates.rb +35 -3
- data/lib/kaal/runtime/scheduler_boot_loader.rb +3 -1
- data/lib/kaal/scheduler_file/job_applier.rb +28 -53
- data/lib/kaal/scheduler_file/loader.rb +1 -1
- data/lib/kaal/sequel_support.rb +2 -2
- data/lib/kaal/version.rb +1 -1
- data/lib/kaal.rb +118 -0
- data/sig/00_types.rbs +12 -0
- data/sig/dependencies.rbs +49 -0
- data/sig/kaal/active_record_support.rbs +23 -0
- data/sig/kaal/backend/adapter.rbs +26 -0
- data/sig/kaal/backend/dispatch_attempt_logger.rbs +17 -0
- data/sig/kaal/backend/dispatch_logging.rbs +23 -0
- data/sig/kaal/backend/dispatch_registry_accessor.rbs +17 -0
- data/sig/kaal/backend/memory_adapter.rbs +33 -0
- data/sig/kaal/backend/mysql.rbs +25 -0
- data/sig/kaal/backend/postgres.rbs +19 -0
- data/sig/kaal/backend/redis_adapter.rbs +41 -0
- data/sig/kaal/backend/sqlite.rbs +19 -0
- data/sig/kaal/cli.rbs +41 -0
- data/sig/kaal/config/backend_factory.rbs +41 -0
- data/sig/kaal/config/configuration.rbs +70 -0
- data/sig/kaal/config/delayed_job_security_policy.rbs +19 -0
- data/sig/kaal/config/file_loader.rbs +35 -0
- data/sig/kaal/config/scheduler_config_error.rbs +4 -0
- data/sig/kaal/config/scheduler_time_zone_resolver.rbs +19 -0
- data/sig/kaal/config.rbs +11 -0
- data/sig/kaal/core/coordinator.rbs +103 -0
- data/sig/kaal/core/enabled_entry_enumerator.rbs +21 -0
- data/sig/kaal/core/occurrence_finder.rbs +9 -0
- data/sig/kaal/core.rbs +9 -0
- data/sig/kaal/definition/database_engine.rbs +25 -0
- data/sig/kaal/definition/memory_engine.rbs +23 -0
- data/sig/kaal/definition/persistence_helpers.rbs +9 -0
- data/sig/kaal/definition/redis_engine.rbs +33 -0
- data/sig/kaal/definition/registry.rbs +29 -0
- data/sig/kaal/definitions/registration_service.rbs +27 -0
- data/sig/kaal/definitions/registry_accessor.rbs +17 -0
- data/sig/kaal/delayed_job/database_engine.rbs +37 -0
- data/sig/kaal/delayed_job/dispatch_failure_logger.rbs +7 -0
- data/sig/kaal/delayed_job/memory_engine.rbs +29 -0
- data/sig/kaal/delayed_job/mysql_version_support.rbs +15 -0
- data/sig/kaal/delayed_job/redis_engine.rbs +31 -0
- data/sig/kaal/delayed_job/registry.rbs +20 -0
- data/sig/kaal/dispatch/database_engine.rbs +39 -0
- data/sig/kaal/dispatch/memory_engine.rbs +23 -0
- data/sig/kaal/dispatch/redis_engine.rbs +25 -0
- data/sig/kaal/dispatch/registry.rbs +11 -0
- data/sig/kaal/internal/active_record/base_record.rbs +8 -0
- data/sig/kaal/internal/active_record/connection_support.rbs +25 -0
- data/sig/kaal/internal/active_record/database_backend.rbs +37 -0
- data/sig/kaal/internal/active_record/definition_record.rbs +8 -0
- data/sig/kaal/internal/active_record/definition_registry.rbs +27 -0
- data/sig/kaal/internal/active_record/delayed_job_record.rbs +8 -0
- data/sig/kaal/internal/active_record/delayed_job_registry.rbs +39 -0
- data/sig/kaal/internal/active_record/dispatch_record.rbs +8 -0
- data/sig/kaal/internal/active_record/dispatch_registry.rbs +43 -0
- data/sig/kaal/internal/active_record/lock_record.rbs +8 -0
- data/sig/kaal/internal/active_record/migration_templates.rbs +17 -0
- data/sig/kaal/internal/active_record/mysql_backend.rbs +45 -0
- data/sig/kaal/internal/active_record/postgres_backend.rbs +41 -0
- data/sig/kaal/internal/active_record.rbs +0 -0
- data/sig/kaal/internal/sequel/database_backend.rbs +39 -0
- data/sig/kaal/internal/sequel/mysql_backend.rbs +47 -0
- data/sig/kaal/internal/sequel/postgres_backend.rbs +43 -0
- data/sig/kaal/internal/sequel.rbs +0 -0
- data/sig/kaal/job_dispatcher.rbs +19 -0
- data/sig/kaal/persistence/database.rbs +19 -0
- data/sig/kaal/persistence/migration_templates.rbs +15 -0
- data/sig/kaal/register_conflict_support.rbs +11 -0
- data/sig/kaal/registry.rbs +44 -0
- data/sig/kaal/runtime/runtime_context.rbs +23 -0
- data/sig/kaal/runtime/scheduler_boot_loader.rbs +23 -0
- data/sig/kaal/runtime/signal_handler_chain.rbs +19 -0
- data/sig/kaal/runtime/signal_handler_installer.rbs +19 -0
- data/sig/kaal/runtime.rbs +11 -0
- data/sig/kaal/scheduler_file/hash_transform.rbs +9 -0
- data/sig/kaal/scheduler_file/helper_bundle.rbs +15 -0
- data/sig/kaal/scheduler_file/job_applier.rbs +43 -0
- data/sig/kaal/scheduler_file/job_normalizer.rbs +27 -0
- data/sig/kaal/scheduler_file/loader.rbs +69 -0
- data/sig/kaal/scheduler_file/payload_loader.rbs +33 -0
- data/sig/kaal/scheduler_file/placeholder_support.rbs +19 -0
- data/sig/kaal/scheduler_file.rbs +9 -0
- data/sig/kaal/sequel_support.rbs +25 -0
- data/sig/kaal/support/hash_tools.rbs +27 -0
- data/sig/kaal/utils/cron_humanizer.rbs +39 -0
- data/sig/kaal/utils/cron_utils.rbs +43 -0
- data/sig/kaal/utils/idempotency_key_generator.rbs +5 -0
- data/sig/kaal/utils.rbs +9 -0
- data/sig/kaal/version.rbs +3 -0
- data/sig/kaal.rbs +145 -0
- metadata +100 -3
- data/config/kaal.rb +0 -15
- /data/config/{scheduler.yml → kaal-scheduler.yml} +0 -0
|
@@ -32,6 +32,10 @@ module Kaal
|
|
|
32
32
|
@definition_registry ||= Kaal::Definition::DatabaseEngine.new(database: @database.connection)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def delayed_store
|
|
36
|
+
@delayed_store ||= Kaal::DelayedJob::DatabaseEngine.new(database: @database.connection, use_skip_locked: true)
|
|
37
|
+
end
|
|
38
|
+
|
|
35
39
|
def acquire(key, _ttl)
|
|
36
40
|
acquired = scalar('SELECT pg_try_advisory_lock(?) AS acquired', self.class.calculate_lock_id(key)) == true
|
|
37
41
|
log_dispatch_attempt(key) if acquired
|
data/lib/kaal/internal/sequel.rb
CHANGED
|
@@ -8,5 +8,6 @@ require 'kaal/internal/sequel/database_backend'
|
|
|
8
8
|
require 'kaal/internal/sequel/postgres_backend'
|
|
9
9
|
require 'kaal/internal/sequel/mysql_backend'
|
|
10
10
|
require 'kaal/definition/database_engine'
|
|
11
|
+
require 'kaal/delayed_job/database_engine'
|
|
11
12
|
require 'kaal/dispatch/database_engine'
|
|
12
13
|
require 'kaal/persistence/database'
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Codevedas Inc. 2025-present
|
|
4
|
+
#
|
|
5
|
+
# This source code is licensed under the MIT license found in the
|
|
6
|
+
# LICENSE file in the root directory of this source tree.
|
|
7
|
+
module Kaal
|
|
8
|
+
# Shared job-class resolution and dispatch rules used by recurring and delayed jobs.
|
|
9
|
+
module JobDispatcher
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def resolve_job_class(job_class_name:, key:, queue: nil, apply_delayed_job_allow_list: true)
|
|
13
|
+
job_class = normalize_job_class(job_class_name, key, apply_delayed_job_allow_list:)
|
|
14
|
+
validate_dispatch_interface(job_class, key, queue)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def normalized_job_class_name(job_class_name:, key:, apply_delayed_job_allow_list: true)
|
|
18
|
+
normalized_job_class_name = normalize_job_class_name(job_class_name)
|
|
19
|
+
raise SchedulerConfigError, "Job class cannot be blank for key '#{key}'" if normalized_job_class_name.empty?
|
|
20
|
+
|
|
21
|
+
return normalized_job_class_name unless apply_delayed_job_allow_list
|
|
22
|
+
|
|
23
|
+
validate_allowed_job_class_name!(job_class_name: normalized_job_class_name, key:)
|
|
24
|
+
normalized_job_class_name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dispatch(job_class:, queue:, args:, key: nil)
|
|
28
|
+
job_class_name = job_class.name
|
|
29
|
+
scheduler_context = key ? " for scheduler job '#{key}'" : ''
|
|
30
|
+
|
|
31
|
+
if queue && !job_class.respond_to?(:set)
|
|
32
|
+
raise SchedulerConfigError,
|
|
33
|
+
"job_class '#{job_class_name}' must respond to .set to use queue #{queue.inspect}#{scheduler_context}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if queue
|
|
37
|
+
job_class.set(queue: queue).perform_later(*args)
|
|
38
|
+
elsif job_class.respond_to?(:perform_later)
|
|
39
|
+
job_class.perform_later(*args)
|
|
40
|
+
elsif job_class.respond_to?(:perform)
|
|
41
|
+
job_class.perform(*args)
|
|
42
|
+
else
|
|
43
|
+
raise SchedulerConfigError,
|
|
44
|
+
"job_class '#{job_class_name}' must respond to .perform, .perform_later, or .set(...).perform_later#{scheduler_context}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def active_job_dispatch?(job_class, queue)
|
|
49
|
+
(queue && job_class.respond_to?(:set)) || job_class.respond_to?(:perform_later)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def normalize_job_class_name(job_class)
|
|
53
|
+
case job_class
|
|
54
|
+
when Module
|
|
55
|
+
job_class.name.to_s.strip
|
|
56
|
+
else
|
|
57
|
+
job_class.to_s.strip
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def normalize_job_class(job_class_name, key, apply_delayed_job_allow_list: true)
|
|
62
|
+
normalized_job_class_name = normalized_job_class_name(
|
|
63
|
+
job_class_name:,
|
|
64
|
+
key:,
|
|
65
|
+
apply_delayed_job_allow_list:
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return job_class_name if job_class_name.is_a?(Module)
|
|
69
|
+
|
|
70
|
+
job_class = begin
|
|
71
|
+
Kaal::Support::HashTools.constantize(normalized_job_class_name)
|
|
72
|
+
rescue NameError
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return job_class if job_class
|
|
77
|
+
|
|
78
|
+
raise SchedulerConfigError, "Unknown job_class #{normalized_job_class_name.inspect} for key '#{key}'"
|
|
79
|
+
end
|
|
80
|
+
private_class_method :normalize_job_class
|
|
81
|
+
|
|
82
|
+
def validate_allowed_job_class_name!(job_class_name:, key:)
|
|
83
|
+
allowed_prefixes = Array(Kaal.configuration.delayed_job_allowed_class_prefixes)
|
|
84
|
+
return if allowed_prefixes.empty?
|
|
85
|
+
return if allowed_prefixes.any? { |prefix| job_class_name.start_with?(prefix) }
|
|
86
|
+
|
|
87
|
+
raise SchedulerConfigError,
|
|
88
|
+
"job_class '#{job_class_name}' for key '#{key}' is not allowed by delayed_job_allowed_class_prefixes"
|
|
89
|
+
end
|
|
90
|
+
private_class_method :validate_allowed_job_class_name!
|
|
91
|
+
|
|
92
|
+
def validate_dispatch_interface(job_class, key, queue)
|
|
93
|
+
queue_present = !queue.nil?
|
|
94
|
+
no_queue = !queue_present
|
|
95
|
+
supports_set = job_class.respond_to?(:set)
|
|
96
|
+
supports_perform_later = job_class.respond_to?(:perform_later)
|
|
97
|
+
supports_perform = job_class.respond_to?(:perform)
|
|
98
|
+
|
|
99
|
+
return job_class if queue_present && supports_set
|
|
100
|
+
return job_class if no_queue && supports_perform_later
|
|
101
|
+
return job_class if no_queue && supports_perform
|
|
102
|
+
|
|
103
|
+
raise SchedulerConfigError,
|
|
104
|
+
"job_class '#{job_class.name}' for key '#{key}' must respond to .perform, .perform_later, or .set(...).perform_later"
|
|
105
|
+
end
|
|
106
|
+
private_class_method :validate_dispatch_interface
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -11,17 +11,21 @@ module Kaal
|
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
13
|
def for_backend(backend)
|
|
14
|
-
|
|
14
|
+
backend_name = backend.to_s
|
|
15
|
+
|
|
16
|
+
case backend_name
|
|
15
17
|
when 'sqlite'
|
|
16
18
|
{
|
|
17
19
|
'001_create_kaal_dispatches.rb' => dispatches_template,
|
|
18
20
|
'002_create_kaal_locks.rb' => locks_template,
|
|
19
|
-
'003_create_kaal_definitions.rb' => definitions_template
|
|
21
|
+
'003_create_kaal_definitions.rb' => definitions_template,
|
|
22
|
+
'004_create_kaal_delayed_jobs.rb' => delayed_jobs_template('sqlite')
|
|
20
23
|
}
|
|
21
24
|
when 'postgres', 'mysql'
|
|
22
25
|
{
|
|
23
26
|
'001_create_kaal_dispatches.rb' => dispatches_template,
|
|
24
|
-
'002_create_kaal_definitions.rb' => definitions_template
|
|
27
|
+
'002_create_kaal_definitions.rb' => definitions_template,
|
|
28
|
+
'003_create_kaal_delayed_jobs.rb' => delayed_jobs_template(backend_name)
|
|
25
29
|
}
|
|
26
30
|
else
|
|
27
31
|
{}
|
|
@@ -92,6 +96,34 @@ module Kaal
|
|
|
92
96
|
end
|
|
93
97
|
RUBY
|
|
94
98
|
end
|
|
99
|
+
|
|
100
|
+
def delayed_jobs_template(backend)
|
|
101
|
+
args_definition =
|
|
102
|
+
if backend == 'mysql'
|
|
103
|
+
'String :args, text: true, null: false'
|
|
104
|
+
else
|
|
105
|
+
"String :args, text: true, null: false, default: '[]'"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
<<~RUBY
|
|
109
|
+
Sequel.migration do
|
|
110
|
+
change do
|
|
111
|
+
create_table?(:kaal_delayed_jobs) do
|
|
112
|
+
primary_key :id
|
|
113
|
+
String :job_id, null: false
|
|
114
|
+
Time :run_at, null: false
|
|
115
|
+
String :job_class, null: false
|
|
116
|
+
#{args_definition}
|
|
117
|
+
String :queue
|
|
118
|
+
Time :created_at, null: false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
add_index :kaal_delayed_jobs, :job_id, unique: true
|
|
122
|
+
add_index :kaal_delayed_jobs, :run_at
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
RUBY
|
|
126
|
+
end
|
|
95
127
|
end
|
|
96
128
|
end
|
|
97
129
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
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
7
|
module Kaal
|
|
8
|
-
# Loads scheduler.yml at framework boot time while respecting missing-file policy.
|
|
8
|
+
# Loads kaal-scheduler.yml at framework boot time while respecting missing-file policy.
|
|
9
9
|
class SchedulerBootLoader
|
|
10
10
|
def initialize(configuration_provider:, logger:, runtime_context:, load_scheduler_file:)
|
|
11
11
|
@configuration_provider = configuration_provider
|
|
@@ -22,6 +22,8 @@ module Kaal
|
|
|
22
22
|
configuration = fetch_configuration
|
|
23
23
|
return unless configuration
|
|
24
24
|
|
|
25
|
+
Kaal.warn_on_risky_configuration!(configuration:, logger: @logger)
|
|
26
|
+
|
|
25
27
|
return load_scheduler_file if configuration.scheduler_missing_file_policy == :error
|
|
26
28
|
|
|
27
29
|
scheduler_path = configuration.scheduler_config_path.to_s.strip
|
|
@@ -77,7 +77,12 @@ module Kaal
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def resolved_job_class(job_class_name:, key:, queue: nil)
|
|
80
|
-
resolve_job_class(
|
|
80
|
+
Kaal::JobDispatcher.resolve_job_class(
|
|
81
|
+
job_class_name:,
|
|
82
|
+
key:,
|
|
83
|
+
queue:,
|
|
84
|
+
apply_delayed_job_allow_list: false
|
|
85
|
+
)
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
def conflict?(key:, existing_definition:)
|
|
@@ -157,7 +162,7 @@ module Kaal
|
|
|
157
162
|
validate_keyword_keys(raw_kwargs, key)
|
|
158
163
|
|
|
159
164
|
resolved_kwargs = raw_kwargs.transform_keys(&:to_sym)
|
|
160
|
-
dispatch_job(job_class, queue, resolved_args, resolved_kwargs)
|
|
165
|
+
dispatch_job(job_class, queue, resolved_args, resolved_kwargs, key)
|
|
161
166
|
end
|
|
162
167
|
end
|
|
163
168
|
|
|
@@ -178,64 +183,34 @@ module Kaal
|
|
|
178
183
|
nil
|
|
179
184
|
end
|
|
180
185
|
|
|
181
|
-
|
|
182
|
-
normalized_job_class_name = job_class_name.to_s.strip
|
|
183
|
-
raise SchedulerConfigError, "Job class cannot be blank for key '#{key}'" if normalized_job_class_name.empty?
|
|
184
|
-
|
|
185
|
-
error_message = "Unknown job_class #{normalized_job_class_name.inspect} for key '#{key}'"
|
|
186
|
-
job_class = begin
|
|
187
|
-
Kaal::Support::HashTools.constantize(normalized_job_class_name)
|
|
188
|
-
rescue NameError
|
|
189
|
-
nil
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
return validate_dispatch_interface(job_class, key, queue) if job_class
|
|
193
|
-
|
|
194
|
-
raise_unknown_job_class(error_message)
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
private :build_callback, :resolve_job_class
|
|
198
|
-
|
|
199
|
-
def dispatch_job(job_class, queue, args, kwargs)
|
|
200
|
-
job_class_name = job_class.name
|
|
201
|
-
|
|
202
|
-
if queue && !job_class.respond_to?(:set)
|
|
203
|
-
raise SchedulerConfigError,
|
|
204
|
-
"job_class '#{job_class_name}' must respond to .set to use queue #{queue.inspect}"
|
|
205
|
-
end
|
|
186
|
+
private :build_callback
|
|
206
187
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
job_class.perform_later(*args, **kwargs)
|
|
211
|
-
elsif job_class.respond_to?(:perform)
|
|
212
|
-
job_class.perform(*args, **kwargs)
|
|
188
|
+
def dispatch_job(job_class, queue, args, kwargs, key)
|
|
189
|
+
if kwargs.empty?
|
|
190
|
+
Kaal::JobDispatcher.dispatch(job_class:, queue:, args:, key:)
|
|
213
191
|
else
|
|
214
|
-
|
|
215
|
-
"job_class '#{job_class_name}' must respond to .perform, .perform_later, or .set(...).perform_later"
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def raise_unknown_job_class(error_message)
|
|
220
|
-
raise SchedulerConfigError, error_message
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def validate_dispatch_interface(job_class, key, queue)
|
|
224
|
-
queue_present = !queue.nil?
|
|
225
|
-
supports_set = job_class.respond_to?(:set)
|
|
226
|
-
supports_perform_later = job_class.respond_to?(:perform_later)
|
|
227
|
-
supports_perform = job_class.respond_to?(:perform)
|
|
192
|
+
job_class_name = job_class.name
|
|
228
193
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
194
|
+
if queue && !job_class.respond_to?(:set)
|
|
195
|
+
raise SchedulerConfigError,
|
|
196
|
+
"job_class '#{job_class_name}' must respond to .set to use queue #{queue.inspect} for scheduler job '#{key}'"
|
|
197
|
+
end
|
|
232
198
|
|
|
233
|
-
|
|
234
|
-
|
|
199
|
+
if queue
|
|
200
|
+
job_class.set(queue: queue).perform_later(*args, **kwargs)
|
|
201
|
+
elsif job_class.respond_to?(:perform_later)
|
|
202
|
+
job_class.perform_later(*args, **kwargs)
|
|
203
|
+
elsif job_class.respond_to?(:perform)
|
|
204
|
+
job_class.perform(*args, **kwargs)
|
|
205
|
+
else
|
|
206
|
+
raise SchedulerConfigError,
|
|
207
|
+
"job_class '#{job_class_name}' must respond to .perform, .perform_later, or .set(...).perform_later for scheduler job '#{key}'"
|
|
208
|
+
end
|
|
209
|
+
end
|
|
235
210
|
end
|
|
236
211
|
|
|
237
212
|
def active_job_dispatch?(job_class, queue)
|
|
238
|
-
|
|
213
|
+
Kaal::JobDispatcher.active_job_dispatch?(job_class, queue)
|
|
239
214
|
end
|
|
240
215
|
end
|
|
241
216
|
end
|
|
@@ -14,7 +14,7 @@ require_relative 'job_normalizer'
|
|
|
14
14
|
require_relative 'job_applier'
|
|
15
15
|
|
|
16
16
|
module Kaal
|
|
17
|
-
# Loads scheduler definitions from config/scheduler.yml and registers them.
|
|
17
|
+
# Loads scheduler definitions from config/kaal-scheduler.yml and registers them.
|
|
18
18
|
class SchedulerFileLoader
|
|
19
19
|
include SchedulerHashTransform
|
|
20
20
|
include SchedulerPlaceholderSupport
|
data/lib/kaal/sequel_support.rb
CHANGED
|
@@ -62,9 +62,9 @@ module Kaal
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def migration_suffixes_for(backend)
|
|
65
|
-
return %w[dispatches locks definitions] if backend.to_s == 'sqlite'
|
|
65
|
+
return %w[dispatches locks definitions delayed_jobs] if backend.to_s == 'sqlite'
|
|
66
66
|
|
|
67
|
-
%w[dispatches definitions]
|
|
67
|
+
%w[dispatches definitions delayed_jobs]
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def timestamp(offset = 0)
|
data/lib/kaal/version.rb
CHANGED
data/lib/kaal.rb
CHANGED
|
@@ -10,6 +10,11 @@ require 'kaal/registry'
|
|
|
10
10
|
require 'kaal/dispatch/registry'
|
|
11
11
|
require 'kaal/dispatch/memory_engine'
|
|
12
12
|
require 'kaal/dispatch/redis_engine'
|
|
13
|
+
require 'kaal/delayed_job/registry'
|
|
14
|
+
require 'kaal/delayed_job/memory_engine'
|
|
15
|
+
require 'kaal/delayed_job/redis_engine'
|
|
16
|
+
require 'kaal/delayed_job/dispatch_failure_logger'
|
|
17
|
+
require 'kaal/delayed_job/mysql_version_support'
|
|
13
18
|
require 'kaal/definition/registry'
|
|
14
19
|
require 'kaal/definition/memory_engine'
|
|
15
20
|
require 'kaal/definition/redis_engine'
|
|
@@ -25,6 +30,7 @@ require 'kaal/persistence/migration_templates'
|
|
|
25
30
|
require 'kaal/sequel_support'
|
|
26
31
|
require 'kaal/active_record_support'
|
|
27
32
|
require 'kaal/utils'
|
|
33
|
+
require 'kaal/job_dispatcher'
|
|
28
34
|
require 'kaal/register_conflict_support'
|
|
29
35
|
require 'kaal/definitions/registry_accessor'
|
|
30
36
|
require 'kaal/definitions/registration_service'
|
|
@@ -56,12 +62,15 @@ module Kaal
|
|
|
56
62
|
@definition_registry = nil
|
|
57
63
|
@definitions_registry_accessor = nil
|
|
58
64
|
@dispatch_registry_accessor = nil
|
|
65
|
+
@risky_configuration_warnings_emitted = {}
|
|
59
66
|
end
|
|
60
67
|
|
|
61
68
|
def reset_registry!
|
|
62
69
|
@registry = Registry.new
|
|
63
70
|
definition_registry = @definition_registry
|
|
64
71
|
definition_registry.clear if definition_registry.respond_to?(:clear)
|
|
72
|
+
delayed_store = configuration.backend&.delayed_store
|
|
73
|
+
delayed_store.clear if delayed_store.respond_to?(:clear)
|
|
65
74
|
@coordinator = nil
|
|
66
75
|
end
|
|
67
76
|
|
|
@@ -79,11 +88,19 @@ module Kaal
|
|
|
79
88
|
yield(configuration) if block_given?
|
|
80
89
|
end
|
|
81
90
|
|
|
91
|
+
def load_config_file!(path: 'config/kaal.yml', runtime_context: RuntimeContext.default)
|
|
92
|
+
Config::FileLoader.new(
|
|
93
|
+
configuration: configuration,
|
|
94
|
+
runtime_context:
|
|
95
|
+
).load(path:)
|
|
96
|
+
end
|
|
97
|
+
|
|
82
98
|
def register(key:, cron:, enqueue:)
|
|
83
99
|
registration_service.call(key:, cron:, enqueue:)
|
|
84
100
|
end
|
|
85
101
|
|
|
86
102
|
def load_scheduler_file!(runtime_context: RuntimeContext.default)
|
|
103
|
+
warn_on_risky_configuration!
|
|
87
104
|
SchedulerFileLoader.new(
|
|
88
105
|
configuration: configuration,
|
|
89
106
|
definition_registry: definition_registry,
|
|
@@ -119,6 +136,7 @@ module Kaal
|
|
|
119
136
|
end
|
|
120
137
|
|
|
121
138
|
def start!
|
|
139
|
+
warn_on_risky_configuration!
|
|
122
140
|
coordinator.start!
|
|
123
141
|
end
|
|
124
142
|
|
|
@@ -138,6 +156,30 @@ module Kaal
|
|
|
138
156
|
coordinator.tick!
|
|
139
157
|
end
|
|
140
158
|
|
|
159
|
+
# Enqueue a one-off delayed job. Delivery is at-most-once after claim.
|
|
160
|
+
def enqueue_at(at:, job_class:, args:, job_id:, queue: nil, connection: nil)
|
|
161
|
+
delayed_store = delayed_store!
|
|
162
|
+
resolved_run_at = normalize_delayed_run_at(at)
|
|
163
|
+
resolved_args = normalize_delayed_args(args)
|
|
164
|
+
resolved_queue = normalize_delayed_queue(queue)
|
|
165
|
+
resolved_job_id = normalize_delayed_job_id(job_id)
|
|
166
|
+
resolved_job_class = JobDispatcher.resolve_job_class(
|
|
167
|
+
job_class_name: job_class,
|
|
168
|
+
key: resolved_job_id,
|
|
169
|
+
queue: resolved_queue
|
|
170
|
+
)
|
|
171
|
+
resolved_job_class_name = JobDispatcher.normalize_job_class_name(resolved_job_class)
|
|
172
|
+
|
|
173
|
+
delayed_store.enqueue(
|
|
174
|
+
job_id: resolved_job_id,
|
|
175
|
+
run_at: resolved_run_at,
|
|
176
|
+
job_class: resolved_job_class_name,
|
|
177
|
+
args: resolved_args,
|
|
178
|
+
queue: resolved_queue,
|
|
179
|
+
connection: connection
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
141
183
|
def with_idempotency(key, fire_time)
|
|
142
184
|
raise ArgumentError, 'block required' unless block_given?
|
|
143
185
|
|
|
@@ -193,6 +235,14 @@ module Kaal
|
|
|
193
235
|
configuration.time_zone = value
|
|
194
236
|
end
|
|
195
237
|
|
|
238
|
+
def delayed_job_allowed_class_prefixes
|
|
239
|
+
configuration.delayed_job_allowed_class_prefixes
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def delayed_job_allowed_class_prefixes=(value)
|
|
243
|
+
configuration.delayed_job_allowed_class_prefixes = value
|
|
244
|
+
end
|
|
245
|
+
|
|
196
246
|
def definition_registry
|
|
197
247
|
definitions_registry_accessor.call
|
|
198
248
|
end
|
|
@@ -205,6 +255,25 @@ module Kaal
|
|
|
205
255
|
configuration.validate!
|
|
206
256
|
end
|
|
207
257
|
|
|
258
|
+
def validation_warnings
|
|
259
|
+
configuration.validation_warnings
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def warn_on_risky_configuration!(configuration: self.configuration, logger: configuration.logger)
|
|
263
|
+
warnings = configuration.validation_warnings
|
|
264
|
+
return [] if warnings.empty?
|
|
265
|
+
|
|
266
|
+
@risky_configuration_warnings_emitted ||= {}
|
|
267
|
+
warnings.each do |warning|
|
|
268
|
+
next if @risky_configuration_warnings_emitted[warning]
|
|
269
|
+
|
|
270
|
+
logger&.warn(warning)
|
|
271
|
+
@risky_configuration_warnings_emitted[warning] = true
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
warnings
|
|
275
|
+
end
|
|
276
|
+
|
|
208
277
|
def valid?(expression)
|
|
209
278
|
CronUtils.valid?(expression)
|
|
210
279
|
end
|
|
@@ -253,5 +322,54 @@ module Kaal
|
|
|
253
322
|
def dispatch_registry_accessor
|
|
254
323
|
@dispatch_registry_accessor ||= Backend::DispatchRegistryAccessor.new(configuration: configuration)
|
|
255
324
|
end
|
|
325
|
+
|
|
326
|
+
def delayed_store!
|
|
327
|
+
delayed_store = configuration.backend&.delayed_store
|
|
328
|
+
return delayed_store if delayed_store
|
|
329
|
+
|
|
330
|
+
raise ArgumentError, 'Configured backend does not support delayed jobs'
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def normalize_delayed_run_at(at)
|
|
334
|
+
time = at.is_a?(Time) ? at : at&.to_time
|
|
335
|
+
ensure_delayed_time!(time)
|
|
336
|
+
|
|
337
|
+
time.utc
|
|
338
|
+
rescue NoMethodError
|
|
339
|
+
ensure_delayed_time!(nil)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def normalize_delayed_args(args)
|
|
343
|
+
raise ArgumentError, 'args must be an array' unless args.is_a?(Array)
|
|
344
|
+
|
|
345
|
+
args.dup
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def normalize_delayed_queue(queue)
|
|
349
|
+
case queue
|
|
350
|
+
when nil
|
|
351
|
+
return nil
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
normalized_queue = queue.to_s.strip
|
|
355
|
+
raise ArgumentError, 'queue cannot be blank' if normalized_queue.empty?
|
|
356
|
+
|
|
357
|
+
normalized_queue
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def normalize_delayed_job_id(job_id)
|
|
361
|
+
normalized_job_id = job_id.to_s.strip
|
|
362
|
+
raise ArgumentError, 'job_id cannot be blank' if normalized_job_id.empty?
|
|
363
|
+
|
|
364
|
+
normalized_job_id
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def invalid_delayed_time_error
|
|
368
|
+
ArgumentError.new('at must be a Time or time-like value')
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def ensure_delayed_time!(time)
|
|
372
|
+
raise invalid_delayed_time_error unless time
|
|
373
|
+
end
|
|
256
374
|
end
|
|
257
375
|
end
|
data/sig/00_types.rbs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Kaal
|
|
2
|
+
interface _RBSOpaque
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
interface _RBSCallable
|
|
6
|
+
def call: (*rbs_any args, **rbs_any kwargs) -> rbs_any
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
type rbs_scalar = nil | bool | Integer | Float | Rational | String | Symbol | Time
|
|
10
|
+
type rbs_hash_key = String | Symbol | Integer
|
|
11
|
+
type rbs_any = rbs_scalar | _RBSOpaque | _RBSCallable | Array[rbs_any] | Hash[rbs_hash_key, rbs_any]
|
|
12
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
class Thor
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class Base
|
|
6
|
+
module Shell
|
|
7
|
+
def self.new: () -> Kaal::_RBSOpaque
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.shell: () -> singleton(Shell)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.package_name: (String name) -> void
|
|
14
|
+
def self.class_option: (*Kaal::rbs_any args, **Kaal::rbs_any kwargs) -> void
|
|
15
|
+
def self.desc: (String usage, String description) -> void
|
|
16
|
+
def self.option: (*Kaal::rbs_any args, **Kaal::rbs_any kwargs) -> void
|
|
17
|
+
def self.no_commands: () { () -> Kaal::rbs_any } -> void
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Redis
|
|
21
|
+
def initialize: (*Kaal::rbs_any args, **Kaal::rbs_any kwargs) -> void
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Sequel
|
|
25
|
+
class Database
|
|
26
|
+
def connection: () -> Kaal::_RBSOpaque
|
|
27
|
+
def database_type: () -> String
|
|
28
|
+
def adapter_scheme: () -> String
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.connect: (*Kaal::rbs_any args, **Kaal::rbs_any kwargs) -> Database
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module ActiveSupport
|
|
35
|
+
module Inflector
|
|
36
|
+
def self.underscore: (String value) -> String
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
module ActiveRecord
|
|
41
|
+
class ConnectionNotEstablished < StandardError
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class RecordNotUnique < StandardError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Base
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Kaal
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
def self.install_postgres_migration: (target_dir: Kaal::rbs_any, ?migration_name: ::String) -> Kaal::rbs_any
|
|
4
|
+
|
|
5
|
+
def self.install_mysql_migration: (target_dir: Kaal::rbs_any, ?migration_name: ::String) -> Kaal::rbs_any
|
|
6
|
+
|
|
7
|
+
def self.install_sqlite_migration: (target_dir: Kaal::rbs_any, ?migration_name: ::String) -> Kaal::rbs_any
|
|
8
|
+
|
|
9
|
+
def self.install_migrations: (target_dir: Kaal::rbs_any, backend: Kaal::rbs_any, ?migration_name: Kaal::rbs_any?, ?time_source: Kaal::rbs_any) -> Kaal::rbs_any
|
|
10
|
+
|
|
11
|
+
def self.require_activerecord!: () -> Kaal::rbs_any
|
|
12
|
+
|
|
13
|
+
def self.normalize_migration_name: (Kaal::rbs_any name, fallback: Kaal::rbs_any) -> Kaal::rbs_any
|
|
14
|
+
|
|
15
|
+
def self.underscore: (Kaal::rbs_any value) -> Kaal::rbs_any
|
|
16
|
+
|
|
17
|
+
def self.default_migration_class_for: (Kaal::rbs_any backend) -> ::String
|
|
18
|
+
|
|
19
|
+
def self.migration_suffixes_for: (Kaal::rbs_any backend) -> (::Array["dispatches" | "locks" | "definitions" | "delayed_jobs"] | ::Array["dispatches" | "definitions" | "delayed_jobs"])
|
|
20
|
+
|
|
21
|
+
def self.alphanumeric?: (Kaal::rbs_any char) -> Kaal::rbs_any
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Kaal
|
|
2
|
+
module Backend
|
|
3
|
+
class Adapter
|
|
4
|
+
def acquire: (Kaal::rbs_any _key, Kaal::rbs_any _ttl) -> Kaal::rbs_any
|
|
5
|
+
|
|
6
|
+
def release: (Kaal::rbs_any _key) -> Kaal::rbs_any
|
|
7
|
+
|
|
8
|
+
def with_lock: (Kaal::rbs_any key, ttl: Kaal::rbs_any) { () -> Kaal::rbs_any } -> (nil | Kaal::rbs_any)
|
|
9
|
+
|
|
10
|
+
def definition_registry: () -> nil
|
|
11
|
+
|
|
12
|
+
def delayed_store: () -> nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class NullAdapter < Adapter
|
|
16
|
+
def acquire: (Kaal::rbs_any _key, Kaal::rbs_any _ttl) -> true
|
|
17
|
+
|
|
18
|
+
def release: (Kaal::rbs_any _key) -> true
|
|
19
|
+
|
|
20
|
+
def with_lock: (Kaal::rbs_any _key, **Kaal::rbs_any) { () -> Kaal::rbs_any } -> Kaal::rbs_any
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class LockAdapterError < StandardError
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Kaal
|
|
2
|
+
module Backend
|
|
3
|
+
class DispatchAttemptLogger
|
|
4
|
+
@configuration: Kaal::rbs_any
|
|
5
|
+
|
|
6
|
+
@dispatch_registry_provider: Kaal::rbs_any
|
|
7
|
+
|
|
8
|
+
@logger: Kaal::rbs_any
|
|
9
|
+
|
|
10
|
+
@node_id_provider: Kaal::rbs_any
|
|
11
|
+
|
|
12
|
+
def initialize: (configuration: Kaal::rbs_any, dispatch_registry_provider: Kaal::rbs_any, ?logger: Kaal::rbs_any?, ?node_id_provider: Kaal::rbs_any) -> void
|
|
13
|
+
|
|
14
|
+
def call: (Kaal::rbs_any lock_key) -> Kaal::rbs_any
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|