solid_queue 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +53 -4
- data/app/models/solid_queue/blocked_execution.rb +1 -1
- data/app/models/solid_queue/claimed_execution.rb +0 -1
- data/app/models/solid_queue/job/clearable.rb +3 -3
- data/app/models/solid_queue/job/executable.rb +2 -6
- data/app/models/solid_queue/job/recurrable.rb +13 -0
- data/app/models/solid_queue/job.rb +1 -1
- data/app/models/solid_queue/recurring_execution.rb +26 -0
- data/db/migrate/20240218110712_create_recurring_executions.rb +14 -0
- data/lib/solid_queue/configuration.rb +14 -5
- data/lib/solid_queue/dispatcher/concurrency_maintenance.rb +44 -0
- data/lib/solid_queue/dispatcher/recurring_schedule.rb +56 -0
- data/lib/solid_queue/dispatcher/recurring_task.rb +85 -0
- data/lib/solid_queue/dispatcher.rb +21 -36
- data/lib/solid_queue/processes/base.rb +1 -18
- data/lib/solid_queue/processes/callbacks.rb +19 -0
- data/lib/solid_queue/processes/poller.rb +28 -0
- data/lib/solid_queue/processes/registrable.rb +4 -5
- data/lib/solid_queue/processes/runnable.rb +31 -46
- data/lib/solid_queue/processes/supervised.rb +4 -0
- data/lib/solid_queue/recurring_tasks/manager.rb +31 -0
- data/lib/solid_queue/recurring_tasks/schedule.rb +58 -0
- data/lib/solid_queue/recurring_tasks/task.rb +87 -0
- data/lib/solid_queue/supervisor.rb +1 -1
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue/worker.rb +11 -12
- data/lib/solid_queue.rb +21 -23
- metadata +113 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c5bed020eb4391f1885c95f2f4373e30d1eacd544e2cfcadf131c8c09079fd0
|
4
|
+
data.tar.gz: 749ac036b072622cd2cc299f1fc5b1a7353a44347beccaa8407157eed8af02ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f39c8aadb36bc8ae5556f3cbd190e6d123ca441b14374b56c207530dd2d37d8dc4880f6e977f6bc2e047b47c886768a3c86d6fefd040a447f6d8f21e28e8a273
|
7
|
+
data.tar.gz: d95c4be96c4388c6558a32f44690e9d92913e47bd01a379f09ef8b3e65a9098a2e578dd68885c36374b27e389c65d67e1100ad4d1c6908e6f675ede75bce8d3a
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Solid Queue is a DB-based queuing backend for [Active Job](https://edgeguides.rubyonrails.org/active_job_basics.html), designed with simplicity and performance in mind.
|
4
4
|
|
5
|
-
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`). _Improvements to logging and instrumentation, a better CLI tool, a way to run within an existing process in "async" mode,
|
5
|
+
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`). _Improvements to logging and instrumentation, a better CLI tool, a way to run within an existing process in "async" mode, and some way of specifying unique jobs are coming very soon._
|
6
6
|
|
7
7
|
Solid Queue can be used with SQL databases such as MySQL, PostgreSQL or SQLite, and it leverages the `FOR UPDATE SKIP LOCKED` clause, if available, to avoid blocking and waiting on locks when polling jobs. It relies on Active Job for retries, discarding, error handling, serialization, or delays, and it's compatible with Ruby on Rails multi-threading.
|
8
8
|
|
@@ -66,6 +66,8 @@ $ bundle exec rake solid_queue:start
|
|
66
66
|
|
67
67
|
This will start processing jobs in all queues using the default configuration. See [below](#configuration) to learn more about configuring Solid Queue.
|
68
68
|
|
69
|
+
For small projects, you can run Solid Queue on the same machine as your webserver. When you're ready to scale, Solid Queue supports horizontal scaling out-of-the-box. You can run Solid Queue on a separate server from your webserver, or even run `bundle exec rake solid_queue:start` on multiple machines at the same time. If you'd like to designate some machines to be only dispatchers or only workers, use `bundle exec rake solid_queue:dispatch` or `bundle exec rake solid_queue:work`, respectively.
|
70
|
+
|
69
71
|
## Requirements
|
70
72
|
Besides Rails 7.1, Solid Queue works best with MySQL 8+ or PostgreSQL 9.5+, as they support `FOR UPDATE SKIP LOCKED`. You can use it with older versions, but in that case, you might run into lock waits if you run multiple workers for the same queue.
|
71
73
|
|
@@ -75,7 +77,7 @@ Besides Rails 7.1, Solid Queue works best with MySQL 8+ or PostgreSQL 9.5+, as t
|
|
75
77
|
|
76
78
|
We have three types of processes in Solid Queue:
|
77
79
|
- _Workers_ are in charge of picking jobs ready to run from queues and processing them. They work off the `solid_queue_ready_executions` table.
|
78
|
-
- _Dispatchers_ are in charge of selecting jobs scheduled to run in the future that are due and _dispatching_ them, which is simply moving them from the `solid_queue_scheduled_executions` table over to the `solid_queue_ready_executions` table so that workers can pick them up. They also do some maintenance work related to concurrency controls.
|
80
|
+
- _Dispatchers_ are in charge of selecting jobs scheduled to run in the future that are due and _dispatching_ them, which is simply moving them from the `solid_queue_scheduled_executions` table over to the `solid_queue_ready_executions` table so that workers can pick them up. They're also in charge of managing [recurring tasks](#recurring-tasks), dispatching jobs to process them according to their schedule. On top of that, they do some maintenance work related to [concurrency controls](#concurrency-controls).
|
79
81
|
- The _supervisor_ forks workers and dispatchers according to the configuration, controls their heartbeats, and sends them signals to stop and start them when needed.
|
80
82
|
|
81
83
|
By default, Solid Queue will try to find your configuration under `config/solid_queue.yml`, but you can set a different path using the environment variable `SOLID_QUEUE_CONFIG`. This is what this configuration looks like:
|
@@ -115,8 +117,10 @@ Everything is optional. If no configuration is provided, Solid Queue will run wi
|
|
115
117
|
This will create a worker fetching jobs from all queues starting with `staging`. The wildcard `*` is only allowed on its own or at the end of a queue name; you can't specify queue names such as `*_some_queue`. These will be ignored.
|
116
118
|
|
117
119
|
Finally, you can combine prefixes with exact names, like `[ staging*, background ]`, and the behaviour with respect to order will be the same as with only exact names.
|
118
|
-
- `threads`: this is the max size of the thread pool that each worker will have to run jobs. Each worker will fetch this number of jobs from their queue(s), at most and will post them to the thread pool to be run. By default, this is `
|
120
|
+
- `threads`: this is the max size of the thread pool that each worker will have to run jobs. Each worker will fetch this number of jobs from their queue(s), at most and will post them to the thread pool to be run. By default, this is `3`. Only workers have this setting.
|
119
121
|
- `processes`: this is the number of worker processes that will be forked by the supervisor with the settings given. By default, this is `1`, just a single process. This setting is useful if you want to dedicate more than one CPU core to a queue or queues with the same configuration. Only workers have this setting.
|
122
|
+
- `concurrency_maintenance`: whether the dispatcher will perform the concurrency maintenance work. This is `true` by default, and it's useful if you don't use any [concurrency controls](#concurrency-controls) and want to disable it or if you run multiple dispatchers and want some of them to just dispatch jobs without doing anything else.
|
123
|
+
- `recurring_tasks`: a list of recurring tasks the dispatcher will manage. Read more details about this one in the [Recurring tasks](#recurring-tasks) section.
|
120
124
|
|
121
125
|
|
122
126
|
### Queue order and priorities
|
@@ -131,7 +135,7 @@ We recommend not mixing queue order with priorities but either choosing one or t
|
|
131
135
|
|
132
136
|
### Threads, processes and signals
|
133
137
|
|
134
|
-
Workers in Solid Queue use a thread pool to run work in multiple threads, configurable via the `threads` parameter above. Besides this, parallelism can be achieved via multiple processes
|
138
|
+
Workers in Solid Queue use a thread pool to run work in multiple threads, configurable via the `threads` parameter above. Besides this, parallelism can be achieved via multiple processes on one machine (configurable via different workers or the `processes` parameter above) or by horizontal scaling.
|
135
139
|
|
136
140
|
The supervisor is in charge of managing these processes, and it responds to the following signals:
|
137
141
|
- `TERM`, `INT`: starts graceful termination. The supervisor will send a `TERM` signal to its supervised processes, and it'll wait up to `SolidQueue.shutdown_timeout` time until they're done. If any supervised processes are still around by then, it'll send a `QUIT` signal to them to indicate they must exit.
|
@@ -263,3 +267,48 @@ Solid Queue has been inspired by [resque](https://github.com/resque/resque) and
|
|
263
267
|
|
264
268
|
## License
|
265
269
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
270
|
+
|
271
|
+
## Recurring tasks
|
272
|
+
Solid Queue supports defining recurring tasks that run at specific times in the future, on a regular basis like cron jobs. These are managed by dispatcher processes and as such, they can be defined in the dispatcher's configuration like this:
|
273
|
+
```yml
|
274
|
+
dispatchers:
|
275
|
+
- polling_interval: 1
|
276
|
+
batch_size: 500
|
277
|
+
recurring_tasks:
|
278
|
+
my_periodic_job:
|
279
|
+
class: MyJob
|
280
|
+
args: [ 42, { status: "custom_status" } ]
|
281
|
+
schedule: every second
|
282
|
+
```
|
283
|
+
`recurring_tasks` is a hash/dictionary, and the key will be the task key internally. Each task needs to have a class, which will be the job class to enqueue, and a schedule. The schedule is parsed using [Fugit](https://github.com/floraison/fugit), so it accepts anything [that Fugit accepts as a cron](https://github.com/floraison/fugit?tab=readme-ov-file#fugitcron). You can also provide arguments to be passed to the job, as a single argument, a hash, or an array of arguments that can also include kwargs as the last element in the array.
|
284
|
+
|
285
|
+
The job in the example configuration above will be enqueued every second as:
|
286
|
+
```ruby
|
287
|
+
MyJob.perform_later(42, status: "custom_status")
|
288
|
+
```
|
289
|
+
|
290
|
+
Tasks are enqueued at their corresponding times by the dispatcher that owns them, and each task schedules the next one. This is pretty much [inspired by what GoodJob does](https://github.com/bensheldon/good_job/blob/994ecff5323bf0337e10464841128fda100750e6/lib/good_job/cron_manager.rb).
|
291
|
+
|
292
|
+
It's possible to run multiple dispatchers with the same `recurring_tasks` configuration. To avoid enqueuing duplicate tasks at the same time, an entry in a new `solid_queue_recurring_executions` table is created in the same transaction as the job is enqueued. This table has a unique index on `task_key` and `run_at`, ensuring only one entry per task per time will be created. This only works if you have `preserve_finished_jobs` set to `true` (the default), and the guarantee applies as long as you keep the jobs around.
|
293
|
+
|
294
|
+
Finally, it's possible to configure jobs that aren't handled by Solid Queue. That's it, you can a have a job like this in your app:
|
295
|
+
```ruby
|
296
|
+
class MyResqueJob < ApplicationJob
|
297
|
+
self.queue_adapter = :resque
|
298
|
+
|
299
|
+
def perform(arg)
|
300
|
+
# ..
|
301
|
+
end
|
302
|
+
end
|
303
|
+
```
|
304
|
+
|
305
|
+
You can still configure this in Solid Queue:
|
306
|
+
```yml
|
307
|
+
dispatchers:
|
308
|
+
- recurring_tasks:
|
309
|
+
my_periodic_resque_job:
|
310
|
+
class: MyResqueJob
|
311
|
+
args: 22
|
312
|
+
schedule: "*/5 * * * *"
|
313
|
+
```
|
314
|
+
and the job will be enqueued via `perform_later` so it'll run in Resque. However, in this case we won't track any `solid_queue_recurring_execution` record for it and there won't be any guarantees that the job is enqueued only once each time.
|
@@ -16,7 +16,6 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
16
16
|
insert_all!(job_data)
|
17
17
|
where(job_id: job_ids, process_id: process_id).load.tap do |claimed|
|
18
18
|
block.call(claimed)
|
19
|
-
SolidQueue.logger.info("[SolidQueue] Claimed #{claimed.size} jobs")
|
20
19
|
end
|
21
20
|
end
|
22
21
|
|
@@ -6,13 +6,13 @@ module SolidQueue
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
scope :clearable, ->(finished_before: SolidQueue.clear_finished_jobs_after.ago) { where.not(finished_at: nil).where(finished_at: ...finished_before) }
|
9
|
+
scope :clearable, ->(finished_before: SolidQueue.clear_finished_jobs_after.ago, class_name: nil) { where.not(finished_at: nil).where(finished_at: ...finished_before).where(class_name.present? ? { class_name: class_name } : {}) }
|
10
10
|
end
|
11
11
|
|
12
12
|
class_methods do
|
13
|
-
def clear_finished_in_batches(batch_size: 500, finished_before: SolidQueue.clear_finished_jobs_after.ago)
|
13
|
+
def clear_finished_in_batches(batch_size: 500, finished_before: SolidQueue.clear_finished_jobs_after.ago, class_name: nil)
|
14
14
|
loop do
|
15
|
-
records_deleted = clearable(finished_before: finished_before).limit(batch_size).delete_all
|
15
|
+
records_deleted = clearable(finished_before: finished_before, class_name: class_name).limit(batch_size).delete_all
|
16
16
|
break if records_deleted == 0
|
17
17
|
end
|
18
18
|
end
|
@@ -6,7 +6,7 @@ module SolidQueue
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
included do
|
9
|
-
include
|
9
|
+
include ConcurrencyControls, Schedulable
|
10
10
|
|
11
11
|
has_one :ready_execution
|
12
12
|
has_one :claimed_execution
|
@@ -78,7 +78,7 @@ module SolidQueue
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def finished!
|
81
|
-
if preserve_finished_jobs?
|
81
|
+
if SolidQueue.preserve_finished_jobs?
|
82
82
|
touch(:finished_at)
|
83
83
|
else
|
84
84
|
destroy!
|
@@ -117,10 +117,6 @@ module SolidQueue
|
|
117
117
|
def execution
|
118
118
|
%w[ ready claimed failed ].reduce(nil) { |acc, status| acc || public_send("#{status}_execution") }
|
119
119
|
end
|
120
|
-
|
121
|
-
def preserve_finished_jobs?
|
122
|
-
SolidQueue.preserve_finished_jobs
|
123
|
-
end
|
124
120
|
end
|
125
121
|
end
|
126
122
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class RecurringExecution < Execution
|
5
|
+
scope :clearable, -> { where.missing(:job) }
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def record(task_key, run_at, &block)
|
9
|
+
transaction do
|
10
|
+
if job_id = block.call
|
11
|
+
create!(job_id: job_id, task_key: task_key, run_at: run_at)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
rescue ActiveRecord::RecordNotUnique
|
15
|
+
SolidQueue.logger.info("[SolidQueue] Skipped recurring task #{task_key} at #{run_at} — already dispatched")
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear_in_batches(batch_size: 500)
|
19
|
+
loop do
|
20
|
+
records_deleted = clearable.limit(batch_size).delete_all
|
21
|
+
break if records_deleted == 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateRecurringExecutions < ActiveRecord::Migration[7.1]
|
2
|
+
def change
|
3
|
+
create_table :solid_queue_recurring_executions do |t|
|
4
|
+
t.references :job, index: { unique: true }, null: false
|
5
|
+
t.string :task_key, null: false
|
6
|
+
t.datetime :run_at, null: false
|
7
|
+
t.datetime :created_at, null: false
|
8
|
+
|
9
|
+
t.index [ :task_key, :run_at ], unique: true
|
10
|
+
end
|
11
|
+
|
12
|
+
add_foreign_key :solid_queue_recurring_executions, :solid_queue_jobs, column: :job_id, on_delete: :cascade
|
13
|
+
end
|
14
|
+
end
|
@@ -4,7 +4,7 @@ module SolidQueue
|
|
4
4
|
class Configuration
|
5
5
|
WORKER_DEFAULTS = {
|
6
6
|
queues: "*",
|
7
|
-
threads:
|
7
|
+
threads: 3,
|
8
8
|
processes: 1,
|
9
9
|
polling_interval: 0.1
|
10
10
|
}
|
@@ -12,7 +12,9 @@ module SolidQueue
|
|
12
12
|
DISPATCHER_DEFAULTS = {
|
13
13
|
batch_size: 500,
|
14
14
|
polling_interval: 1,
|
15
|
-
|
15
|
+
concurrency_maintenance: true,
|
16
|
+
concurrency_maintenance_interval: 600,
|
17
|
+
recurring_tasks: []
|
16
18
|
}
|
17
19
|
|
18
20
|
def initialize(mode: :work, load_from: nil)
|
@@ -33,7 +35,7 @@ module SolidQueue
|
|
33
35
|
if mode.in? %i[ work all]
|
34
36
|
workers_options.flat_map do |worker_options|
|
35
37
|
processes = worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
|
36
|
-
processes.times.
|
38
|
+
processes.times.map { Worker.new(**worker_options.with_defaults(WORKER_DEFAULTS)) }
|
37
39
|
end
|
38
40
|
else
|
39
41
|
[]
|
@@ -42,8 +44,10 @@ module SolidQueue
|
|
42
44
|
|
43
45
|
def dispatchers
|
44
46
|
if mode.in? %i[ dispatch all]
|
45
|
-
dispatchers_options.
|
46
|
-
|
47
|
+
dispatchers_options.map do |dispatcher_options|
|
48
|
+
recurring_tasks = parse_recurring_tasks dispatcher_options[:recurring_tasks]
|
49
|
+
|
50
|
+
Dispatcher.new **dispatcher_options.merge(recurring_tasks: recurring_tasks).with_defaults(DISPATCHER_DEFAULTS)
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -73,6 +77,11 @@ module SolidQueue
|
|
73
77
|
.map { |options| options.dup.symbolize_keys }
|
74
78
|
end
|
75
79
|
|
80
|
+
def parse_recurring_tasks(tasks)
|
81
|
+
Array(tasks).map do |id, options|
|
82
|
+
Dispatcher::RecurringTask.from_configuration(id, **options)
|
83
|
+
end.select(&:valid?)
|
84
|
+
end
|
76
85
|
|
77
86
|
def load_config_from(file_or_hash)
|
78
87
|
case file_or_hash
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Dispatcher::ConcurrencyMaintenance
|
5
|
+
include AppExecutor
|
6
|
+
|
7
|
+
attr_reader :interval, :batch_size
|
8
|
+
|
9
|
+
def initialize(interval, batch_size)
|
10
|
+
@interval = interval
|
11
|
+
@batch_size = batch_size
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
@concurrency_maintenance_task = Concurrent::TimerTask.new(run_now: true, execution_interval: interval) do
|
16
|
+
expire_semaphores
|
17
|
+
unblock_blocked_executions
|
18
|
+
end
|
19
|
+
|
20
|
+
@concurrency_maintenance_task.add_observer do |_, _, error|
|
21
|
+
handle_thread_error(error) if error
|
22
|
+
end
|
23
|
+
|
24
|
+
@concurrency_maintenance_task.execute
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
@concurrency_maintenance_task.shutdown
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def expire_semaphores
|
33
|
+
wrap_in_app_executor do
|
34
|
+
Semaphore.expired.in_batches(of: batch_size, &:delete_all)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def unblock_blocked_executions
|
39
|
+
wrap_in_app_executor do
|
40
|
+
BlockedExecution.unblock(batch_size)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Dispatcher::RecurringSchedule
|
5
|
+
include AppExecutor
|
6
|
+
|
7
|
+
attr_reader :configured_tasks, :scheduled_tasks
|
8
|
+
|
9
|
+
def initialize(tasks)
|
10
|
+
@configured_tasks = Array(tasks).map { |task| Dispatcher::RecurringTask.wrap(task) }
|
11
|
+
@scheduled_tasks = Concurrent::Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_tasks
|
15
|
+
configured_tasks.each do |task|
|
16
|
+
load_task(task)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_task(task)
|
21
|
+
scheduled_tasks[task.key] = schedule(task)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unload_tasks
|
25
|
+
scheduled_tasks.values.each(&:cancel)
|
26
|
+
scheduled_tasks.clear
|
27
|
+
end
|
28
|
+
|
29
|
+
def tasks
|
30
|
+
configured_tasks.each_with_object({}) { |task, hsh| hsh[task.key] = task.to_h }
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
configured_tasks.map(&:to_s).join(" | ")
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def schedule(task)
|
39
|
+
scheduled_task = Concurrent::ScheduledTask.new(task.delay_from_now, args: [ self, task, task.next_time ]) do |thread_schedule, thread_task, thread_task_run_at|
|
40
|
+
thread_schedule.load_task(thread_task)
|
41
|
+
|
42
|
+
wrap_in_app_executor do
|
43
|
+
thread_task.enqueue(at: thread_task_run_at)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
scheduled_task.add_observer do |_, _, error|
|
48
|
+
# Don't notify on task cancellation before execution, as this will happen normally
|
49
|
+
# as part of unloading tasks
|
50
|
+
handle_thread_error(error) if error && !error.is_a?(Concurrent::CancelledOperationError)
|
51
|
+
end
|
52
|
+
|
53
|
+
scheduled_task.tap(&:execute)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "fugit"
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Dispatcher::RecurringTask
|
5
|
+
class << self
|
6
|
+
def wrap(args)
|
7
|
+
args.is_a?(self) ? args : from_configuration(args.first, **args.second)
|
8
|
+
end
|
9
|
+
|
10
|
+
def from_configuration(key, **options)
|
11
|
+
new(key, class_name: options[:class], schedule: options[:schedule], arguments: options[:args])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :key, :schedule, :class_name, :arguments
|
16
|
+
|
17
|
+
def initialize(key, class_name:, schedule:, arguments: nil)
|
18
|
+
@key = key
|
19
|
+
@class_name = class_name
|
20
|
+
@schedule = schedule
|
21
|
+
@arguments = Array(arguments)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delay_from_now
|
25
|
+
[ (next_time - Time.current).to_f, 0 ].max
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_time
|
29
|
+
parsed_schedule.next_time.utc
|
30
|
+
end
|
31
|
+
|
32
|
+
def enqueue(at:)
|
33
|
+
if using_solid_queue_adapter?
|
34
|
+
perform_later_and_record(run_at: at)
|
35
|
+
else
|
36
|
+
perform_later
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid?
|
41
|
+
parsed_schedule.instance_of?(Fugit::Cron)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
"#{class_name}.perform_later(#{arguments.map(&:inspect).join(",")}) [ #{parsed_schedule.original.to_s} ]"
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_h
|
49
|
+
{
|
50
|
+
schedule: schedule,
|
51
|
+
class_name: class_name,
|
52
|
+
arguments: arguments
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def using_solid_queue_adapter?
|
58
|
+
job_class.queue_adapter_name.inquiry.solid_queue?
|
59
|
+
end
|
60
|
+
|
61
|
+
def perform_later_and_record(run_at:)
|
62
|
+
RecurringExecution.record(key, run_at) { perform_later.provider_job_id }
|
63
|
+
end
|
64
|
+
|
65
|
+
def perform_later
|
66
|
+
job_class.perform_later(*arguments_with_kwargs)
|
67
|
+
end
|
68
|
+
|
69
|
+
def arguments_with_kwargs
|
70
|
+
if arguments.last.is_a?(Hash)
|
71
|
+
arguments[0...-1] + [ Hash.ruby2_keywords_hash(arguments.last) ]
|
72
|
+
else
|
73
|
+
arguments
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parsed_schedule
|
78
|
+
@parsed_schedule ||= Fugit.parse(schedule)
|
79
|
+
end
|
80
|
+
|
81
|
+
def job_class
|
82
|
+
@job_class ||= class_name.safe_constantize
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -2,72 +2,57 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Dispatcher < Processes::Base
|
5
|
-
include Processes::
|
5
|
+
include Processes::Poller
|
6
6
|
|
7
|
-
attr_accessor :batch_size, :
|
7
|
+
attr_accessor :batch_size, :concurrency_maintenance, :recurring_schedule
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
after_boot :start_concurrency_maintenance, :load_recurring_schedule
|
10
|
+
before_shutdown :stop_concurrency_maintenance, :unload_recurring_schedule
|
11
11
|
|
12
12
|
def initialize(**options)
|
13
13
|
options = options.dup.with_defaults(SolidQueue::Configuration::DISPATCHER_DEFAULTS)
|
14
14
|
|
15
15
|
@batch_size = options[:batch_size]
|
16
16
|
@polling_interval = options[:polling_interval]
|
17
|
-
|
17
|
+
|
18
|
+
@concurrency_maintenance = ConcurrencyMaintenance.new(options[:concurrency_maintenance_interval], options[:batch_size]) if options[:concurrency_maintenance]
|
19
|
+
@recurring_schedule = RecurringSchedule.new(options[:recurring_tasks])
|
18
20
|
end
|
19
21
|
|
20
22
|
private
|
21
|
-
def
|
23
|
+
def poll
|
22
24
|
batch = dispatch_next_batch
|
23
|
-
|
24
|
-
unless batch.size > 0
|
25
|
-
procline "waiting"
|
26
|
-
interruptible_sleep(polling_interval)
|
27
|
-
end
|
25
|
+
batch.size
|
28
26
|
end
|
29
27
|
|
30
28
|
def dispatch_next_batch
|
31
29
|
with_polling_volume do
|
32
|
-
|
30
|
+
ScheduledExecution.dispatch_next_batch(batch_size)
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
def
|
37
|
-
|
38
|
-
expire_semaphores
|
39
|
-
unblock_blocked_executions
|
40
|
-
end
|
41
|
-
|
42
|
-
@concurrency_maintenance_task.add_observer do |_, _, error|
|
43
|
-
handle_thread_error(error) if error
|
44
|
-
end
|
45
|
-
|
46
|
-
@concurrency_maintenance_task.execute
|
34
|
+
def start_concurrency_maintenance
|
35
|
+
concurrency_maintenance&.start
|
47
36
|
end
|
48
37
|
|
49
|
-
def
|
50
|
-
|
38
|
+
def load_recurring_schedule
|
39
|
+
recurring_schedule.load_tasks
|
51
40
|
end
|
52
41
|
|
53
|
-
def
|
54
|
-
|
55
|
-
Semaphore.expired.in_batches(of: batch_size, &:delete_all)
|
56
|
-
end
|
42
|
+
def stop_concurrency_maintenance
|
43
|
+
concurrency_maintenance&.stop
|
57
44
|
end
|
58
45
|
|
59
|
-
def
|
60
|
-
|
61
|
-
BlockedExecution.unblock(batch_size)
|
62
|
-
end
|
46
|
+
def unload_recurring_schedule
|
47
|
+
recurring_schedule.unload_tasks
|
63
48
|
end
|
64
49
|
|
65
|
-
def
|
66
|
-
|
50
|
+
def set_procline
|
51
|
+
procline "waiting"
|
67
52
|
end
|
68
53
|
|
69
54
|
def metadata
|
70
|
-
super.merge(batch_size: batch_size)
|
55
|
+
super.merge(batch_size: batch_size, concurrency_maintenance_interval: concurrency_maintenance&.interval, recurring_schedule: recurring_schedule.tasks.presence )
|
71
56
|
end
|
72
57
|
end
|
73
58
|
end
|
@@ -3,25 +3,8 @@
|
|
3
3
|
module SolidQueue
|
4
4
|
module Processes
|
5
5
|
class Base
|
6
|
-
include
|
7
|
-
define_callbacks :boot, :shutdown
|
8
|
-
|
6
|
+
include Callbacks # Defines callbacks needed by other concerns
|
9
7
|
include AppExecutor, Registrable, Interruptible, Procline
|
10
|
-
|
11
|
-
private
|
12
|
-
def observe_initial_delay
|
13
|
-
interruptible_sleep(initial_jitter)
|
14
|
-
end
|
15
|
-
|
16
|
-
def boot
|
17
|
-
end
|
18
|
-
|
19
|
-
def shutdown
|
20
|
-
end
|
21
|
-
|
22
|
-
def initial_jitter
|
23
|
-
0
|
24
|
-
end
|
25
8
|
end
|
26
9
|
end
|
27
10
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue::Processes
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
extend ActiveModel::Callbacks
|
9
|
+
define_model_callbacks :boot, :shutdown
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def boot
|
14
|
+
end
|
15
|
+
|
16
|
+
def shutdown
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -4,11 +4,39 @@ module SolidQueue::Processes
|
|
4
4
|
module Poller
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
+
include Runnable
|
8
|
+
|
7
9
|
included do
|
8
10
|
attr_accessor :polling_interval
|
9
11
|
end
|
10
12
|
|
11
13
|
private
|
14
|
+
def run
|
15
|
+
if mode.async?
|
16
|
+
@thread = Thread.new { start_loop }
|
17
|
+
else
|
18
|
+
start_loop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_loop
|
23
|
+
loop do
|
24
|
+
break if shutting_down?
|
25
|
+
|
26
|
+
wrap_in_app_executor do
|
27
|
+
unless poll > 0
|
28
|
+
interruptible_sleep(polling_interval)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
ensure
|
33
|
+
run_callbacks(:shutdown) { shutdown }
|
34
|
+
end
|
35
|
+
|
36
|
+
def poll
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
12
40
|
def with_polling_volume
|
13
41
|
if SolidQueue.silence_polling?
|
14
42
|
ActiveRecord::Base.logger.silence { yield }
|
@@ -5,11 +5,10 @@ module SolidQueue::Processes
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
|
9
|
-
set_callback :boot, :after, :launch_heartbeat
|
8
|
+
after_boot :register, :launch_heartbeat
|
10
9
|
|
11
|
-
|
12
|
-
|
10
|
+
before_shutdown :stop_heartbeat
|
11
|
+
after_shutdown :deregister
|
13
12
|
end
|
14
13
|
|
15
14
|
def inspect
|
@@ -26,7 +25,7 @@ module SolidQueue::Processes
|
|
26
25
|
pid: process_pid,
|
27
26
|
hostname: hostname,
|
28
27
|
supervisor: try(:supervisor),
|
29
|
-
metadata: metadata
|
28
|
+
metadata: metadata.compact
|
30
29
|
end
|
31
30
|
|
32
31
|
def deregister
|
@@ -8,11 +8,9 @@ module SolidQueue::Processes
|
|
8
8
|
|
9
9
|
def start
|
10
10
|
@stopping = false
|
11
|
-
|
12
|
-
observe_initial_delay
|
13
11
|
run_callbacks(:boot) { boot }
|
14
12
|
|
15
|
-
|
13
|
+
run
|
16
14
|
end
|
17
15
|
|
18
16
|
def stop
|
@@ -20,60 +18,47 @@ module SolidQueue::Processes
|
|
20
18
|
@thread&.join
|
21
19
|
end
|
22
20
|
|
23
|
-
|
24
|
-
|
21
|
+
private
|
22
|
+
DEFAULT_MODE = :async
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def mode
|
25
|
+
(@mode || DEFAULT_MODE).to_s.inquiry
|
26
|
+
end
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
def boot
|
29
|
+
if supervised?
|
30
|
+
register_signal_handlers
|
31
|
+
set_procline
|
32
|
+
end
|
34
33
|
|
35
|
-
|
36
|
-
if mode.async?
|
37
|
-
@thread = Thread.new { do_start_loop }
|
38
|
-
else
|
39
|
-
do_start_loop
|
34
|
+
SolidQueue.logger.info("[SolidQueue] Starting #{self}")
|
40
35
|
end
|
41
|
-
end
|
42
36
|
|
43
|
-
|
44
|
-
|
45
|
-
break if shutting_down?
|
46
|
-
|
47
|
-
wrap_in_app_executor do
|
48
|
-
run
|
49
|
-
end
|
37
|
+
def shutting_down?
|
38
|
+
stopping? || supervisor_went_away? || finished?
|
50
39
|
end
|
51
|
-
ensure
|
52
|
-
run_callbacks(:shutdown) { shutdown }
|
53
|
-
end
|
54
40
|
|
55
|
-
|
56
|
-
|
57
|
-
|
41
|
+
def run
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
58
44
|
|
59
|
-
|
60
|
-
|
61
|
-
|
45
|
+
def stopping?
|
46
|
+
@stopping
|
47
|
+
end
|
62
48
|
|
63
|
-
|
64
|
-
|
65
|
-
|
49
|
+
def finished?
|
50
|
+
running_inline? && all_work_completed?
|
51
|
+
end
|
66
52
|
|
67
|
-
|
68
|
-
|
69
|
-
|
53
|
+
def all_work_completed?
|
54
|
+
false
|
55
|
+
end
|
70
56
|
|
71
|
-
|
72
|
-
|
73
|
-
end
|
57
|
+
def set_procline
|
58
|
+
end
|
74
59
|
|
75
|
-
|
76
|
-
|
77
|
-
|
60
|
+
def running_inline?
|
61
|
+
mode.inline?
|
62
|
+
end
|
78
63
|
end
|
79
64
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
module RecurringTasks
|
5
|
+
class Manager < Processes::Base
|
6
|
+
include Processes::Runnable
|
7
|
+
|
8
|
+
attr_accessor :schedule
|
9
|
+
|
10
|
+
after_boot :load_schedule
|
11
|
+
before_shutdown :unload_schedule
|
12
|
+
|
13
|
+
def initialize(tasks)
|
14
|
+
@schedule = Schedule.new(tasks)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def load_schedule
|
19
|
+
schedule.load_tasks
|
20
|
+
end
|
21
|
+
|
22
|
+
def unload_schedule
|
23
|
+
schedule.unload_tasks
|
24
|
+
end
|
25
|
+
|
26
|
+
def metadata
|
27
|
+
super.merge(schedule: schedule.tasks)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
module RecurringTasks
|
5
|
+
class Schedule
|
6
|
+
include AppExecutor
|
7
|
+
|
8
|
+
attr_reader :configured_tasks, :scheduled_tasks
|
9
|
+
|
10
|
+
def initialize(tasks)
|
11
|
+
@configured_tasks = Array(tasks).map { |task| Task.wrap(task) }
|
12
|
+
@scheduled_tasks = Concurrent::Hash.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_tasks
|
16
|
+
configured_tasks.each do |task|
|
17
|
+
load_task(task)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_task(task)
|
22
|
+
scheduled_tasks[task.key] = schedule(task)
|
23
|
+
end
|
24
|
+
|
25
|
+
def unload_tasks
|
26
|
+
scheduled_tasks.values.each(&:cancel)
|
27
|
+
scheduled_tasks.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
def tasks
|
31
|
+
configured_tasks.each_with_object({}) { |task, hsh| hsh[task.key] = task.to_h }
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
configured_tasks.map(&:to_s).join(" | ")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def schedule(task)
|
40
|
+
scheduled_task = Concurrent::ScheduledTask.new(task.delay_from_now, args: [ self, task, task.next_time ]) do |thread_schedule, thread_task, thread_task_run_at|
|
41
|
+
thread_schedule.load_task(thread_task)
|
42
|
+
|
43
|
+
wrap_in_app_executor do
|
44
|
+
thread_task.enqueue(at: thread_task_run_at)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
scheduled_task.add_observer do |_, _, error|
|
49
|
+
# Don't notify on task cancellation before execution, as this will happen normally
|
50
|
+
# as part of unloading tasks
|
51
|
+
handle_thread_error(error) if error && !error.is_a?(Concurrent::CancelledOperationError)
|
52
|
+
end
|
53
|
+
|
54
|
+
scheduled_task.tap(&:execute)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "fugit"
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
module RecurringTasks
|
5
|
+
class Task
|
6
|
+
class << self
|
7
|
+
def wrap(args)
|
8
|
+
args.is_a?(self) ? args : from_configuration(args.first, **args.second)
|
9
|
+
end
|
10
|
+
|
11
|
+
def from_configuration(key, **options)
|
12
|
+
new(key, class_name: options[:class], schedule: options[:schedule], arguments: options[:args])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :key, :schedule, :class_name, :arguments
|
17
|
+
|
18
|
+
def initialize(key, class_name:, schedule:, arguments: nil)
|
19
|
+
@key = key
|
20
|
+
@class_name = class_name
|
21
|
+
@schedule = schedule
|
22
|
+
@arguments = Array(arguments)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delay_from_now
|
26
|
+
[ (next_time - Time.current).to_f, 0 ].max
|
27
|
+
end
|
28
|
+
|
29
|
+
def next_time
|
30
|
+
parsed_schedule.next_time.utc
|
31
|
+
end
|
32
|
+
|
33
|
+
def enqueue(at:)
|
34
|
+
if using_solid_queue_adapter?
|
35
|
+
perform_later_and_record(run_at: at)
|
36
|
+
else
|
37
|
+
perform_later
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid?
|
42
|
+
parsed_schedule.instance_of?(Fugit::Cron)
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
"#{class_name}.perform_later(#{arguments.map(&:inspect).join(",")}) [ #{parsed_schedule.original.to_s} ]"
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_h
|
50
|
+
{
|
51
|
+
schedule: schedule,
|
52
|
+
class_name: class_name,
|
53
|
+
arguments: arguments
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def using_solid_queue_adapter?
|
59
|
+
job_class.queue_adapter_name.inquiry.solid_queue?
|
60
|
+
end
|
61
|
+
|
62
|
+
def perform_later_and_record(run_at:)
|
63
|
+
RecurringExecution.record(key, run_at) { perform_later.provider_job_id }
|
64
|
+
end
|
65
|
+
|
66
|
+
def perform_later
|
67
|
+
job_class.perform_later(*arguments_with_kwargs)
|
68
|
+
end
|
69
|
+
|
70
|
+
def arguments_with_kwargs
|
71
|
+
if arguments.last.is_a?(Hash)
|
72
|
+
arguments[0...-1] + [ Hash.ruby2_keywords_hash(arguments.last) ]
|
73
|
+
else
|
74
|
+
arguments
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def parsed_schedule
|
79
|
+
@parsed_schedule ||= Fugit.parse(schedule)
|
80
|
+
end
|
81
|
+
|
82
|
+
def job_class
|
83
|
+
@job_class ||= class_name.safe_constantize
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/solid_queue/version.rb
CHANGED
data/lib/solid_queue/worker.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Worker < Processes::Base
|
5
|
-
include Processes::
|
5
|
+
include Processes::Poller
|
6
6
|
|
7
7
|
attr_accessor :queues, :pool
|
8
8
|
|
@@ -15,22 +15,17 @@ module SolidQueue
|
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
if polled_executions.size > 0
|
22
|
-
procline "performing #{polled_executions.count} jobs"
|
23
|
-
|
24
|
-
polled_executions.each do |execution|
|
18
|
+
def poll
|
19
|
+
claim_executions.then do |executions|
|
20
|
+
executions.each do |execution|
|
25
21
|
pool.post(execution)
|
26
22
|
end
|
27
|
-
|
28
|
-
|
29
|
-
interruptible_sleep(polling_interval)
|
23
|
+
|
24
|
+
executions.size
|
30
25
|
end
|
31
26
|
end
|
32
27
|
|
33
|
-
def
|
28
|
+
def claim_executions
|
34
29
|
with_polling_volume do
|
35
30
|
SolidQueue::ReadyExecution.claim(queues, pool.idle_threads, process.id)
|
36
31
|
end
|
@@ -47,6 +42,10 @@ module SolidQueue
|
|
47
42
|
SolidQueue::ReadyExecution.aggregated_count_across(queues).zero?
|
48
43
|
end
|
49
44
|
|
45
|
+
def set_procline
|
46
|
+
procline "waiting for jobs in #{queues.join(",")}"
|
47
|
+
end
|
48
|
+
|
50
49
|
def metadata
|
51
50
|
super.merge(queues: queues.join(","), thread_pool_size: pool.size)
|
52
51
|
end
|
data/lib/solid_queue.rb
CHANGED
@@ -3,24 +3,16 @@
|
|
3
3
|
require "solid_queue/version"
|
4
4
|
require "solid_queue/engine"
|
5
5
|
|
6
|
-
require "active_job
|
7
|
-
require "active_job/
|
8
|
-
|
9
|
-
require "
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
require "solid_queue/processes/base"
|
17
|
-
require "solid_queue/processes/runnable"
|
18
|
-
require "solid_queue/processes/signals"
|
19
|
-
require "solid_queue/configuration"
|
20
|
-
require "solid_queue/pool"
|
21
|
-
require "solid_queue/worker"
|
22
|
-
require "solid_queue/dispatcher"
|
23
|
-
require "solid_queue/supervisor"
|
6
|
+
require "active_job"
|
7
|
+
require "active_job/queue_adapters"
|
8
|
+
|
9
|
+
require "zeitwerk"
|
10
|
+
|
11
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
12
|
+
loader.ignore("#{__dir__}/solid_queue/tasks.rb")
|
13
|
+
loader.ignore("#{__dir__}/generators")
|
14
|
+
loader.ignore("#{__dir__}/puma")
|
15
|
+
loader.setup
|
24
16
|
|
25
17
|
module SolidQueue
|
26
18
|
mattr_accessor :logger, default: ActiveSupport::Logger.new($stdout)
|
@@ -42,11 +34,17 @@ module SolidQueue
|
|
42
34
|
mattr_accessor :clear_finished_jobs_after, default: 1.day
|
43
35
|
mattr_accessor :default_concurrency_control_period, default: 3.minutes
|
44
36
|
|
45
|
-
|
46
|
-
supervisor
|
47
|
-
|
37
|
+
class << self
|
38
|
+
def supervisor?
|
39
|
+
supervisor
|
40
|
+
end
|
41
|
+
|
42
|
+
def silence_polling?
|
43
|
+
silence_polling
|
44
|
+
end
|
48
45
|
|
49
|
-
|
50
|
-
|
46
|
+
def preserve_finished_jobs?
|
47
|
+
preserve_finished_jobs
|
48
|
+
end
|
51
49
|
end
|
52
50
|
end
|
metadata
CHANGED
@@ -1,29 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rosa Gutierrez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '7.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activejob
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '7.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: railties
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '7.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
25
53
|
- !ruby/object:Gem::Version
|
26
54
|
version: '7.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: concurrent-ruby
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.2
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.2.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: fugit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.9.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.9.0
|
27
83
|
- !ruby/object:Gem::Dependency
|
28
84
|
name: debug
|
29
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +122,48 @@ dependencies:
|
|
66
122
|
- - ">="
|
67
123
|
- !ruby/object:Gem::Version
|
68
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: mysql2
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: pg
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: sqlite3
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
69
167
|
description: Database-backed Active Job backend.
|
70
168
|
email:
|
71
169
|
- rosa@37signals.com
|
@@ -86,6 +184,7 @@ files:
|
|
86
184
|
- app/models/solid_queue/job/clearable.rb
|
87
185
|
- app/models/solid_queue/job/concurrency_controls.rb
|
88
186
|
- app/models/solid_queue/job/executable.rb
|
187
|
+
- app/models/solid_queue/job/recurrable.rb
|
89
188
|
- app/models/solid_queue/job/schedulable.rb
|
90
189
|
- app/models/solid_queue/pause.rb
|
91
190
|
- app/models/solid_queue/process.rb
|
@@ -94,11 +193,13 @@ files:
|
|
94
193
|
- app/models/solid_queue/queue_selector.rb
|
95
194
|
- app/models/solid_queue/ready_execution.rb
|
96
195
|
- app/models/solid_queue/record.rb
|
196
|
+
- app/models/solid_queue/recurring_execution.rb
|
97
197
|
- app/models/solid_queue/scheduled_execution.rb
|
98
198
|
- app/models/solid_queue/semaphore.rb
|
99
199
|
- config/routes.rb
|
100
200
|
- db/migrate/20231211200639_create_solid_queue_tables.rb
|
101
201
|
- db/migrate/20240110143450_add_missing_index_to_blocked_executions.rb
|
202
|
+
- db/migrate/20240218110712_create_recurring_executions.rb
|
102
203
|
- lib/active_job/concurrency_controls.rb
|
103
204
|
- lib/active_job/queue_adapters/solid_queue_adapter.rb
|
104
205
|
- lib/generators/solid_queue/install/USAGE
|
@@ -109,9 +210,13 @@ files:
|
|
109
210
|
- lib/solid_queue/app_executor.rb
|
110
211
|
- lib/solid_queue/configuration.rb
|
111
212
|
- lib/solid_queue/dispatcher.rb
|
213
|
+
- lib/solid_queue/dispatcher/concurrency_maintenance.rb
|
214
|
+
- lib/solid_queue/dispatcher/recurring_schedule.rb
|
215
|
+
- lib/solid_queue/dispatcher/recurring_task.rb
|
112
216
|
- lib/solid_queue/engine.rb
|
113
217
|
- lib/solid_queue/pool.rb
|
114
218
|
- lib/solid_queue/processes/base.rb
|
219
|
+
- lib/solid_queue/processes/callbacks.rb
|
115
220
|
- lib/solid_queue/processes/interruptible.rb
|
116
221
|
- lib/solid_queue/processes/pidfile.rb
|
117
222
|
- lib/solid_queue/processes/poller.rb
|
@@ -120,6 +225,9 @@ files:
|
|
120
225
|
- lib/solid_queue/processes/runnable.rb
|
121
226
|
- lib/solid_queue/processes/signals.rb
|
122
227
|
- lib/solid_queue/processes/supervised.rb
|
228
|
+
- lib/solid_queue/recurring_tasks/manager.rb
|
229
|
+
- lib/solid_queue/recurring_tasks/schedule.rb
|
230
|
+
- lib/solid_queue/recurring_tasks/task.rb
|
123
231
|
- lib/solid_queue/supervisor.rb
|
124
232
|
- lib/solid_queue/tasks.rb
|
125
233
|
- lib/solid_queue/version.rb
|