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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d8c12714ca483db0abe733287ae543ecd206e58c02b228983aa0fb506671542
|
|
4
|
+
data.tar.gz: 3236ad6f7721d80424a3b725542c66f0633a3a21ed42d52514a5294b75013461
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7213b5670154c87e4115761d62ee9e5955bd84e520ac3d3dc44dc5118c7840aeb6b6d82ad7f71aa8fe1b7fb32430abd59f636c2d14a0450054af6de7c695105
|
|
7
|
+
data.tar.gz: 790385b5f77017e8001e2c9c3e38484acdab8e733dc5cd15d1bf0a39c6c2074fa9756c7226328446bc69b9044895b0644bdb521725b6992780265d5d0a296fb0
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Distributed cron scheduling for plain Ruby.
|
|
4
4
|
|
|
5
|
-
`kaal` is the core engine gem. It owns scheduler/runtime behavior, the registry APIs, the plain Ruby CLI, and the optional SQL backend surfaces.
|
|
5
|
+
`kaal` is the core engine gem. It owns scheduler/runtime behavior, the registry APIs, the plain Ruby CLI, delayed-job dispatch, and the optional SQL backend surfaces.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -21,43 +21,43 @@ bundle exec kaal init --backend=memory
|
|
|
21
21
|
|
|
22
22
|
`kaal init` creates:
|
|
23
23
|
|
|
24
|
-
- `config/kaal.
|
|
25
|
-
- `config/scheduler.yml`
|
|
24
|
+
- `config/kaal.yml`
|
|
25
|
+
- `config/kaal-scheduler.yml`
|
|
26
26
|
|
|
27
27
|
Supported backends:
|
|
28
28
|
|
|
29
29
|
- `memory`
|
|
30
30
|
- `redis`
|
|
31
31
|
|
|
32
|
-
If you want SQL persistence instead, add the runtime libraries your app uses, such as `sequel`, `activerecord`, `sqlite3`, `pg`, or `mysql2`, then
|
|
32
|
+
If you want SQL persistence instead, add the runtime libraries your app uses, such as `sequel`, `activerecord`, `sqlite3`, `pg`, or `mysql2`, then set `backend: sqlite/postgres/mysql` plus `backend_config` in `config/kaal.yml`.
|
|
33
33
|
|
|
34
34
|
## Configuration
|
|
35
35
|
|
|
36
|
-
Generated `config/kaal.
|
|
36
|
+
Generated `config/kaal.yml` is the primary entrypoint:
|
|
37
37
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
```yaml
|
|
39
|
+
defaults:
|
|
40
|
+
backend: memory
|
|
41
|
+
namespace: kaal
|
|
42
|
+
tick_interval: 5
|
|
43
|
+
window_lookback: 120
|
|
44
|
+
window_lookahead: 0
|
|
45
|
+
lease_ttl: 125
|
|
46
|
+
scheduler_config_path: config/kaal-scheduler.yml
|
|
47
|
+
enable_dispatch_recovery: true
|
|
48
|
+
enable_log_dispatch_registry: false
|
|
49
|
+
delayed_job_allowed_class_prefixes: []
|
|
50
|
+
backend_config: {}
|
|
48
51
|
```
|
|
49
52
|
|
|
50
53
|
Redis path:
|
|
51
54
|
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
config.backend = Kaal::Backend::RedisAdapter.new(redis)
|
|
59
|
-
config.scheduler_config_path = 'config/scheduler.yml'
|
|
60
|
-
end
|
|
55
|
+
```yaml
|
|
56
|
+
defaults:
|
|
57
|
+
backend: redis
|
|
58
|
+
scheduler_config_path: config/kaal-scheduler.yml
|
|
59
|
+
backend_config:
|
|
60
|
+
url: redis://127.0.0.1:6379/0
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Time zone behavior is explicit:
|
|
@@ -67,7 +67,7 @@ Time zone behavior is explicit:
|
|
|
67
67
|
|
|
68
68
|
## Scheduler File
|
|
69
69
|
|
|
70
|
-
Default scheduler definitions live at `config/scheduler.yml`:
|
|
70
|
+
Default scheduler definitions live at `config/kaal-scheduler.yml`:
|
|
71
71
|
|
|
72
72
|
```yaml
|
|
73
73
|
defaults:
|
|
@@ -110,6 +110,8 @@ REDIS_URL=redis://127.0.0.1:6379/0 bin/rspec-e2e redis
|
|
|
110
110
|
|
|
111
111
|
## Runtime API
|
|
112
112
|
|
|
113
|
+
Recurring jobs:
|
|
114
|
+
|
|
113
115
|
```ruby
|
|
114
116
|
Kaal.register(
|
|
115
117
|
key: 'reports:daily',
|
|
@@ -122,6 +124,35 @@ Kaal.register(
|
|
|
122
124
|
Kaal.start!
|
|
123
125
|
```
|
|
124
126
|
|
|
127
|
+
Delayed jobs:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
Kaal.enqueue_at(
|
|
131
|
+
at: Time.now.utc + 300,
|
|
132
|
+
job_class: "InvoiceReminderJob",
|
|
133
|
+
args: [123],
|
|
134
|
+
queue: "mailers",
|
|
135
|
+
job_id: "invoice-reminder:123"
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Rules shared by the runtime surface:
|
|
140
|
+
|
|
141
|
+
- delayed jobs use `job_id` as their identity and require it to be unique while pending
|
|
142
|
+
- delayed-job `args` are positional only
|
|
143
|
+
- recurring and delayed jobs share the same job-class dispatch rules
|
|
144
|
+
- string job classes are constantized and class or module values are used directly
|
|
145
|
+
|
|
146
|
+
To restrict delayed-job class names:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
Kaal.configure do |config|
|
|
150
|
+
config.delayed_job_allowed_class_prefixes = ["Reports::", "Billing::"]
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
An empty `delayed_job_allowed_class_prefixes` list leaves delayed-job class resolution unrestricted. That is reasonable for local or trusted deployments. On shared Redis or SQL backends in production, set a restrictive prefix list.
|
|
155
|
+
|
|
125
156
|
## SQL Backends
|
|
126
157
|
|
|
127
158
|
Use the explicit SQL backends when you want persisted registries:
|
|
@@ -130,3 +161,7 @@ Use the explicit SQL backends when you want persisted registries:
|
|
|
130
161
|
- `Kaal::Backend::Postgres`
|
|
131
162
|
- `Kaal::Backend::MySQL`
|
|
132
163
|
- `kaal-rails` for Rails-native install and auto-wiring
|
|
164
|
+
|
|
165
|
+
For SQL-backed deployments, run the generated migrations so `kaal_delayed_jobs` exists alongside the recurring scheduler tables.
|
|
166
|
+
|
|
167
|
+
Postgres and supported MySQL versions claim due delayed jobs with `SKIP LOCKED`. Older SQL paths still preserve correctness with delete confirmation, and Kaal adds a small pre-claim jitter there to reduce multi-node contention.
|
data/config/kaal.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
defaults:
|
|
2
|
+
backend: memory
|
|
3
|
+
namespace: kaal
|
|
4
|
+
tick_interval: 5
|
|
5
|
+
window_lookback: 120
|
|
6
|
+
window_lookahead: 0
|
|
7
|
+
lease_ttl: 125
|
|
8
|
+
scheduler_config_path: config/kaal-scheduler.yml
|
|
9
|
+
enable_dispatch_recovery: true
|
|
10
|
+
enable_log_dispatch_registry: false
|
|
11
|
+
delayed_job_allowed_class_prefixes: []
|
|
12
|
+
backend_config: {}
|
|
@@ -68,9 +68,9 @@ module Kaal
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def migration_suffixes_for(backend)
|
|
71
|
-
return %w[dispatches locks definitions] if backend.to_s == 'sqlite'
|
|
71
|
+
return %w[dispatches locks definitions delayed_jobs] if backend.to_s == 'sqlite'
|
|
72
72
|
|
|
73
|
-
%w[dispatches definitions]
|
|
73
|
+
%w[dispatches definitions delayed_jobs]
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def alphanumeric?(char)
|
data/lib/kaal/backend/adapter.rb
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# LICENSE file in the root directory of this source tree.
|
|
7
7
|
require_relative 'dispatch_logging'
|
|
8
8
|
require_relative '../definition/memory_engine'
|
|
9
|
+
require_relative '../delayed_job/memory_engine'
|
|
9
10
|
|
|
10
11
|
module Kaal
|
|
11
12
|
module Backend
|
|
@@ -50,6 +51,10 @@ module Kaal
|
|
|
50
51
|
@definition_registry ||= Kaal::Definition::MemoryEngine.new
|
|
51
52
|
end
|
|
52
53
|
|
|
54
|
+
def delayed_store
|
|
55
|
+
@delayed_store ||= Kaal::DelayedJob::MemoryEngine.new
|
|
56
|
+
end
|
|
57
|
+
|
|
53
58
|
##
|
|
54
59
|
# Attempt to acquire a lock in memory.
|
|
55
60
|
#
|
data/lib/kaal/backend/mysql.rb
CHANGED
|
@@ -8,16 +8,20 @@ module Kaal
|
|
|
8
8
|
module Backend
|
|
9
9
|
# MySQL-backed backend for either Sequel or Active Record persistence.
|
|
10
10
|
class MySQL < Adapter
|
|
11
|
-
|
|
11
|
+
UNSET_SKIP_LOCKED_SUPPORT = Object.new.freeze
|
|
12
|
+
|
|
13
|
+
def initialize(database: nil, connection: nil, namespace: nil,
|
|
14
|
+
use_skip_locked: UNSET_SKIP_LOCKED_SUPPORT)
|
|
12
15
|
super()
|
|
16
|
+
backend_class = self.class
|
|
13
17
|
@engine = if database
|
|
14
18
|
Kaal::Sequel.require_sequel!
|
|
15
19
|
require 'kaal/internal/sequel'
|
|
16
|
-
|
|
20
|
+
backend_class.send(:build_sequel_backend, database, namespace, use_skip_locked)
|
|
17
21
|
else
|
|
18
22
|
Kaal::ActiveRecord.require_activerecord!
|
|
19
23
|
require 'kaal/internal/active_record'
|
|
20
|
-
|
|
24
|
+
backend_class.send(:build_active_record_backend, connection, namespace, use_skip_locked)
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
|
|
@@ -29,6 +33,10 @@ module Kaal
|
|
|
29
33
|
@engine.definition_registry
|
|
30
34
|
end
|
|
31
35
|
|
|
36
|
+
def delayed_store
|
|
37
|
+
@engine.delayed_store
|
|
38
|
+
end
|
|
39
|
+
|
|
32
40
|
def acquire(key, ttl)
|
|
33
41
|
@engine.acquire(key, ttl)
|
|
34
42
|
end
|
|
@@ -36,6 +44,20 @@ module Kaal
|
|
|
36
44
|
def release(key)
|
|
37
45
|
@engine.release(key)
|
|
38
46
|
end
|
|
47
|
+
|
|
48
|
+
def self.build_sequel_backend(database, namespace, use_skip_locked)
|
|
49
|
+
return Kaal::Internal::Sequel::MySQLBackend.new(database, namespace:) if use_skip_locked.equal?(UNSET_SKIP_LOCKED_SUPPORT)
|
|
50
|
+
|
|
51
|
+
Kaal::Internal::Sequel::MySQLBackend.new(database, namespace:, use_skip_locked:)
|
|
52
|
+
end
|
|
53
|
+
private_class_method :build_sequel_backend
|
|
54
|
+
|
|
55
|
+
def self.build_active_record_backend(connection, namespace, use_skip_locked)
|
|
56
|
+
return Kaal::Internal::ActiveRecord::MySQLBackend.new(connection, namespace:) if use_skip_locked.equal?(UNSET_SKIP_LOCKED_SUPPORT)
|
|
57
|
+
|
|
58
|
+
Kaal::Internal::ActiveRecord::MySQLBackend.new(connection, namespace:, use_skip_locked:)
|
|
59
|
+
end
|
|
60
|
+
private_class_method :build_active_record_backend
|
|
39
61
|
end
|
|
40
62
|
end
|
|
41
63
|
end
|
|
@@ -8,7 +8,7 @@ module Kaal
|
|
|
8
8
|
module Backend
|
|
9
9
|
# PostgreSQL-backed backend for either Sequel or Active Record persistence.
|
|
10
10
|
class Postgres < Adapter
|
|
11
|
-
def initialize(database: nil, connection: nil, namespace: nil
|
|
11
|
+
def initialize(database: nil, connection: nil, namespace: nil)
|
|
12
12
|
super()
|
|
13
13
|
@engine = if database
|
|
14
14
|
Kaal::Sequel.require_sequel!
|
|
@@ -17,7 +17,7 @@ module Kaal
|
|
|
17
17
|
else
|
|
18
18
|
Kaal::ActiveRecord.require_activerecord!
|
|
19
19
|
require 'kaal/internal/active_record'
|
|
20
|
-
Kaal::Internal::ActiveRecord::PostgresBackend.new(connection, namespace
|
|
20
|
+
Kaal::Internal::ActiveRecord::PostgresBackend.new(connection, namespace:)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -29,6 +29,10 @@ module Kaal
|
|
|
29
29
|
@engine.definition_registry
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def delayed_store
|
|
33
|
+
@engine.delayed_store
|
|
34
|
+
end
|
|
35
|
+
|
|
32
36
|
def acquire(key, ttl)
|
|
33
37
|
@engine.acquire(key, ttl)
|
|
34
38
|
end
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
require 'securerandom'
|
|
8
8
|
require_relative 'dispatch_logging'
|
|
9
9
|
require_relative '../definition/redis_engine'
|
|
10
|
+
require_relative '../delayed_job/redis_engine'
|
|
10
11
|
|
|
11
12
|
module Kaal
|
|
12
13
|
module Backend
|
|
@@ -65,6 +66,10 @@ module Kaal
|
|
|
65
66
|
@definition_registry ||= Kaal::Definition::RedisEngine.new(@redis, namespace: @namespace)
|
|
66
67
|
end
|
|
67
68
|
|
|
69
|
+
def delayed_store
|
|
70
|
+
@delayed_store ||= Kaal::DelayedJob::RedisEngine.new(@redis, namespace: @namespace)
|
|
71
|
+
end
|
|
72
|
+
|
|
68
73
|
##
|
|
69
74
|
# Attempt to acquire a distributed lock in Redis.
|
|
70
75
|
#
|
data/lib/kaal/backend/sqlite.rb
CHANGED
data/lib/kaal/cli.rb
CHANGED
|
@@ -19,9 +19,10 @@ module Kaal
|
|
|
19
19
|
def load_project!
|
|
20
20
|
Kaal.reset_configuration!
|
|
21
21
|
Kaal.reset_registry!
|
|
22
|
-
load config_path
|
|
23
22
|
runtime_context = RuntimeContext.default(root_path: root_path)
|
|
24
|
-
Kaal.
|
|
23
|
+
Kaal.load_config_file!(path: config_path, runtime_context:)
|
|
24
|
+
Kaal.warn_on_risky_configuration!
|
|
25
|
+
Kaal.load_scheduler_file!(runtime_context: runtime_context)
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def root_path
|
|
@@ -32,42 +33,46 @@ module Kaal
|
|
|
32
33
|
config = options[:config]
|
|
33
34
|
return File.expand_path(config) if config
|
|
34
35
|
|
|
35
|
-
File.join(root_path, 'config', 'kaal.
|
|
36
|
+
File.join(root_path, 'config', 'kaal.yml')
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def scheduler_path
|
|
39
|
-
File.join(root_path, 'config', 'scheduler.yml')
|
|
40
|
+
File.join(root_path, 'config', 'kaal-scheduler.yml')
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
def render_config_template(backend)
|
|
43
44
|
case backend
|
|
44
45
|
when 'memory'
|
|
45
|
-
<<~
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
<<~YAML
|
|
47
|
+
defaults:
|
|
48
|
+
backend: memory
|
|
49
|
+
namespace: kaal
|
|
50
|
+
tick_interval: 5
|
|
51
|
+
window_lookback: 120
|
|
52
|
+
window_lookahead: 0
|
|
53
|
+
lease_ttl: 125
|
|
54
|
+
scheduler_config_path: config/kaal-scheduler.yml
|
|
55
|
+
enable_dispatch_recovery: true
|
|
56
|
+
enable_log_dispatch_registry: false
|
|
57
|
+
delayed_job_allowed_class_prefixes: []
|
|
58
|
+
backend_config: {}
|
|
59
|
+
YAML
|
|
56
60
|
when 'redis'
|
|
57
|
-
<<~
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
config.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
<<~YAML
|
|
62
|
+
defaults:
|
|
63
|
+
backend: redis
|
|
64
|
+
namespace: kaal
|
|
65
|
+
tick_interval: 5
|
|
66
|
+
window_lookback: 120
|
|
67
|
+
window_lookahead: 0
|
|
68
|
+
lease_ttl: 125
|
|
69
|
+
scheduler_config_path: config/kaal-scheduler.yml
|
|
70
|
+
enable_dispatch_recovery: true
|
|
71
|
+
enable_log_dispatch_registry: false
|
|
72
|
+
delayed_job_allowed_class_prefixes: []
|
|
73
|
+
backend_config:
|
|
74
|
+
url: redis://127.0.0.1:6379/0
|
|
75
|
+
YAML
|
|
71
76
|
else
|
|
72
77
|
raise Thor::Error, "Unsupported backend '#{backend}'"
|
|
73
78
|
end
|
|
@@ -94,9 +99,9 @@ module Kaal
|
|
|
94
99
|
package_name 'kaal'
|
|
95
100
|
|
|
96
101
|
class_option :root, type: :string, default: Dir.pwd, desc: 'Project root'
|
|
97
|
-
class_option :config, type: :string, desc: 'Path to config/kaal.
|
|
102
|
+
class_option :config, type: :string, desc: 'Path to config/kaal.yml'
|
|
98
103
|
|
|
99
|
-
desc 'init', 'Generate config/kaal.
|
|
104
|
+
desc 'init', 'Generate config/kaal.yml and config/kaal-scheduler.yml'
|
|
100
105
|
option :backend, type: :string, default: 'memory', enum: %w[memory redis]
|
|
101
106
|
def init
|
|
102
107
|
root = File.expand_path(options[:root])
|
|
@@ -104,8 +109,8 @@ module Kaal
|
|
|
104
109
|
writer = self.class
|
|
105
110
|
FileUtils.mkdir_p(File.join(root, 'config'))
|
|
106
111
|
|
|
107
|
-
writer.write_file(File.join(root, 'config', 'kaal.
|
|
108
|
-
writer.write_file(File.join(root, 'config', 'scheduler.yml'), scheduler_template)
|
|
112
|
+
writer.write_file(File.join(root, 'config', 'kaal.yml'), render_config_template(backend))
|
|
113
|
+
writer.write_file(File.join(root, 'config', 'kaal-scheduler.yml'), scheduler_template)
|
|
109
114
|
|
|
110
115
|
say("Initialized Kaal project for #{backend} backend")
|
|
111
116
|
end
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
require 'fileutils'
|
|
8
|
+
require 'pathname'
|
|
9
|
+
require 'uri'
|
|
10
|
+
|
|
11
|
+
module Kaal
|
|
12
|
+
module Config
|
|
13
|
+
# Builds backend adapter instances from symbolic runtime configuration.
|
|
14
|
+
module BackendFactory
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
SUPPORTED_BACKENDS = %w[memory redis sqlite postgres mysql].freeze
|
|
18
|
+
|
|
19
|
+
def normalize_name(name)
|
|
20
|
+
normalized = name.to_s.strip.downcase
|
|
21
|
+
return nil if normalized.empty?
|
|
22
|
+
|
|
23
|
+
normalized = 'postgres' if normalized == 'postgresql'
|
|
24
|
+
normalized = 'mysql' if normalized == 'trilogy'
|
|
25
|
+
return normalized if SUPPORTED_BACKENDS.include?(normalized)
|
|
26
|
+
|
|
27
|
+
raise Kaal::ConfigurationError, "Unsupported backend #{name.inspect}; use memory, redis, sqlite, postgres, or mysql"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def build(name, backend_config:, namespace:, runtime_context: nil)
|
|
31
|
+
backend_name = normalize_name(name)
|
|
32
|
+
config = normalize_backend_config(backend_config)
|
|
33
|
+
|
|
34
|
+
case backend_name
|
|
35
|
+
when 'memory'
|
|
36
|
+
Kaal::Backend::MemoryAdapter.new
|
|
37
|
+
when 'redis'
|
|
38
|
+
build_redis_backend(config, namespace)
|
|
39
|
+
when 'sqlite'
|
|
40
|
+
build_sqlite_backend(config, namespace, runtime_context)
|
|
41
|
+
when 'postgres'
|
|
42
|
+
build_postgres_backend(config, namespace)
|
|
43
|
+
when 'mysql'
|
|
44
|
+
build_mysql_backend(config, namespace)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def normalize_backend_config(backend_config)
|
|
49
|
+
hash = backend_config.is_a?(Hash) ? backend_config : {}
|
|
50
|
+
Kaal::Support::HashTools.symbolize_keys(Kaal::Support::HashTools.deep_dup(hash))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_redis_backend(config, namespace)
|
|
54
|
+
require_redis!
|
|
55
|
+
|
|
56
|
+
url = string_value(config[:url])
|
|
57
|
+
raise Kaal::ConfigurationError, 'redis backend requires backend_config.url or KAAL_BACKEND_URL' if url.empty?
|
|
58
|
+
|
|
59
|
+
Kaal::Backend::RedisAdapter.new(::Redis.new(url: url), namespace:)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_sqlite_backend(config, namespace, runtime_context)
|
|
63
|
+
return Kaal::Backend::SQLite.new(connection: build_sqlite_connection(config[:connection], runtime_context), namespace:) if config.key?(:connection)
|
|
64
|
+
|
|
65
|
+
url = string_value(config[:url])
|
|
66
|
+
database = string_value(config[:database])
|
|
67
|
+
target = url.empty? ? database : url
|
|
68
|
+
raise Kaal::ConfigurationError, 'sqlite backend requires backend_config.url, backend_config.database, or KAAL_BACKEND_URL' if target.empty?
|
|
69
|
+
|
|
70
|
+
require_sequel!
|
|
71
|
+
Kaal::Backend::SQLite.new(
|
|
72
|
+
database: sequel_sqlite_database(target, runtime_context),
|
|
73
|
+
namespace:
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_postgres_backend(config, namespace)
|
|
78
|
+
if config.key?(:connection)
|
|
79
|
+
return Kaal::Backend::Postgres.new(connection: normalize_connection_hash(config[:connection], 'postgresql', nil),
|
|
80
|
+
namespace:)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
url = string_value(config[:url])
|
|
84
|
+
raise Kaal::ConfigurationError, 'postgres backend requires backend_config.url or KAAL_BACKEND_URL' if url.empty?
|
|
85
|
+
|
|
86
|
+
require_sequel!
|
|
87
|
+
Kaal::Backend::Postgres.new(database: ::Sequel.connect(url), namespace:)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def build_mysql_backend(config, namespace)
|
|
91
|
+
use_skip_locked = config[:use_skip_locked]
|
|
92
|
+
skip_locked_configured = config.key?(:use_skip_locked)
|
|
93
|
+
|
|
94
|
+
if config.key?(:connection)
|
|
95
|
+
connection = normalize_connection_hash(config[:connection], 'mysql2', nil)
|
|
96
|
+
return Kaal::Backend::MySQL.new(connection:, namespace:) unless skip_locked_configured
|
|
97
|
+
|
|
98
|
+
return Kaal::Backend::MySQL.new(connection:, namespace:, use_skip_locked:)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
url = string_value(config[:url])
|
|
102
|
+
raise Kaal::ConfigurationError, 'mysql backend requires backend_config.url or KAAL_BACKEND_URL' if url.empty?
|
|
103
|
+
|
|
104
|
+
require_sequel!
|
|
105
|
+
database = ::Sequel.connect(url)
|
|
106
|
+
return Kaal::Backend::MySQL.new(database:, namespace:) unless skip_locked_configured
|
|
107
|
+
|
|
108
|
+
Kaal::Backend::MySQL.new(database:, namespace:, use_skip_locked:)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_sqlite_connection(connection, runtime_context)
|
|
112
|
+
normalize_connection_hash(connection, 'sqlite3', runtime_context)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def normalize_connection_hash(connection, default_adapter, runtime_context)
|
|
116
|
+
case connection
|
|
117
|
+
when String
|
|
118
|
+
connection
|
|
119
|
+
when Hash
|
|
120
|
+
normalized = Kaal::Support::HashTools.symbolize_keys(Kaal::Support::HashTools.deep_dup(connection))
|
|
121
|
+
adapter = string_value(normalized[:adapter])
|
|
122
|
+
normalized_adapter = adapter.empty? ? default_adapter : adapter
|
|
123
|
+
normalized[:adapter] = normalized_adapter
|
|
124
|
+
normalized[:database] = resolve_sqlite_database_path(normalized[:database], runtime_context) if normalized_adapter == 'sqlite3'
|
|
125
|
+
normalized
|
|
126
|
+
else
|
|
127
|
+
raise Kaal::ConfigurationError, 'backend_config.connection must be a URL string or hash'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def resolve_sqlite_database_path(database, runtime_context)
|
|
132
|
+
value = string_value(database)
|
|
133
|
+
return value if value.empty?
|
|
134
|
+
return value if sqlite_uri?(value)
|
|
135
|
+
return ensure_sqlite_directory!(value) if Pathname.new(value).absolute?
|
|
136
|
+
|
|
137
|
+
resolved = runtime_context ? runtime_context.resolve_path(value) : value
|
|
138
|
+
ensure_sqlite_directory!(resolved)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def sqlite_uri?(value)
|
|
142
|
+
value.start_with?('sqlite:', 'file:')
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def sequel_sqlite_database(target, runtime_context)
|
|
146
|
+
return ::Sequel.connect(target) if sqlite_uri?(target)
|
|
147
|
+
|
|
148
|
+
::Sequel.connect(adapter: 'sqlite', database: resolve_sqlite_database_path(target, runtime_context))
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def ensure_sqlite_directory!(database_path)
|
|
152
|
+
directory = File.dirname(database_path)
|
|
153
|
+
FileUtils.mkdir_p(directory) unless directory == '.' || directory.empty?
|
|
154
|
+
database_path
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def adapter_name_for_error(adapter)
|
|
158
|
+
adapter == 'postgresql' ? 'postgres' : 'mysql'
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def string_value(value)
|
|
162
|
+
value.to_s.strip
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def require_redis!
|
|
166
|
+
require 'redis'
|
|
167
|
+
rescue LoadError => e
|
|
168
|
+
raise LoadError, "#{e.message}. Add `gem 'redis'` to your Gemfile to use the Redis-backed Kaal adapter.", cause: e
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def require_sequel!
|
|
172
|
+
require 'sequel'
|
|
173
|
+
rescue LoadError => e
|
|
174
|
+
raise LoadError, "#{e.message}. Add `gem 'sequel'` to your Gemfile to use Sequel-backed Kaal SQL support.", cause: e
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|