solid_queue 0.8.2 → 0.9.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 +69 -30
- data/Rakefile +13 -0
- data/UPGRADING.md +5 -0
- data/app/jobs/solid_queue/recurring_job.rb +9 -0
- data/app/models/solid_queue/process/prunable.rb +2 -2
- data/app/models/solid_queue/recurring_task.rb +35 -8
- data/lib/generators/solid_queue/install/USAGE +1 -0
- data/lib/generators/solid_queue/install/install_generator.rb +2 -1
- data/lib/generators/solid_queue/install/templates/config/recurring.yml +9 -0
- data/lib/solid_queue/cli.rb +12 -2
- data/lib/solid_queue/configuration.rb +67 -42
- data/lib/solid_queue/dispatcher.rb +4 -5
- data/lib/solid_queue/log_subscriber.rb +2 -2
- data/lib/solid_queue/processes/base.rb +9 -0
- data/lib/solid_queue/processes/poller.rb +0 -3
- data/lib/solid_queue/processes/registrable.rb +6 -3
- data/lib/solid_queue/processes/runnable.rb +11 -8
- data/lib/solid_queue/processes/supervised.rb +0 -1
- data/lib/solid_queue/{dispatcher → scheduler}/recurring_schedule.rb +1 -1
- data/lib/solid_queue/scheduler.rb +53 -0
- data/lib/solid_queue/supervisor/maintenance.rb +1 -1
- data/lib/solid_queue/supervisor.rb +3 -8
- data/lib/solid_queue/version.rb +1 -1
- metadata +13 -6
- /data/lib/generators/solid_queue/install/templates/config/{solid_queue.yml → queue.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: b0a582a0c2b6b0025baed737e35b41dbe2dc687b0fbd7a221909b6350321a717
|
4
|
+
data.tar.gz: 85053c1ab6f8b7d5b66855631a003e789306499eba71d100ae91a32e39757b97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 630b5c980be4b1e152544e46eb38922706b620765ffb1b9c18ff81a9c00a221be2d0ed2f723ecd7d39391a01119785b3be42db408f7e48a6b0afd67ab62088eb
|
7
|
+
data.tar.gz: 4451b96428dfbaa8213d9fff225dfeaa5826aa10d6ab68151effbba01df866c8c19110e4c6f147a25b70fa34827c6a6c03381b1fb7681934b036791ba4f16faa
|
data/README.md
CHANGED
@@ -2,9 +2,9 @@
|
|
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`).
|
5
|
+
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, recurring jobs, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`).
|
6
6
|
|
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.
|
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's multi-threading.
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
@@ -13,9 +13,9 @@ Solid Queue is configured by default in new Rails 8 applications. But if you're
|
|
13
13
|
1. `bundle add solid_queue`
|
14
14
|
2. `bin/rails solid_queue:install`
|
15
15
|
|
16
|
-
This will configure Solid Queue as the production Active Job backend, create `config/
|
16
|
+
This will configure Solid Queue as the production Active Job backend, create the configuration files `config/queue.yml` and `config/recurring.yml`, and create the `db/queue_schema.rb`. It'll also create a `bin/jobs` executable wrapper that you can use to start Solid Queue.
|
17
17
|
|
18
|
-
|
18
|
+
Once you've done that, you will then have to add the configuration for the queue database in `config/database.yml`. If you're using sqlite, it'll look like this:
|
19
19
|
|
20
20
|
```yaml
|
21
21
|
production:
|
@@ -43,12 +43,24 @@ production:
|
|
43
43
|
migrations_paths: db/queue_migrate
|
44
44
|
```
|
45
45
|
|
46
|
+
Note: Calling `bin/rails solid_queue:install` will automatically add `config.solid_queue.connects_to = { database: { writing: :queue } }` to `config/environments/production.rb`, so no additional configuration is needed there (although you must make sure that you use the `queue` name in `database.yml` for this to match!). But if you want to use Solid Queue in a different environment (like staging or even development), you'll have to manually add that `config.solid_queue.connects_to` line to the respective environment file. And, as always, make sure that the name you're using for the database in `config/database.yml` matches the name you use in `config.solid_queue.connects_to`.
|
47
|
+
|
46
48
|
Then run `db:prepare` in production to ensure the database is created and the schema is loaded.
|
47
49
|
|
48
50
|
Now you're ready to start processing jobs by running `bin/jobs` on the server that's doing the work. This will start processing jobs in all queues using the default configuration. See [below](#configuration) to learn more about configuring Solid Queue.
|
49
51
|
|
50
52
|
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 `bin/jobs` on multiple machines at the same time. Depending on the configuration, you can designate some machines to run only dispatchers or only workers. See the [configuration](#configuration) section for more details on this.
|
51
53
|
|
54
|
+
### Single database configuration
|
55
|
+
|
56
|
+
It's also possibile to use one single database for both production data:
|
57
|
+
|
58
|
+
1. Copy the contents of `db/queue_schema.rb` into a normal migration and delete `db/queue_schema.rb`
|
59
|
+
2. Remove `config.solid_queue.connects_to` from `production.rb`
|
60
|
+
3. Migrate your database. You are ready to run `bin/jobs`
|
61
|
+
|
62
|
+
You won't have multiple databases, so `database.yml` doesn't need to have primary and queue database.
|
63
|
+
|
52
64
|
## Incremental adoption
|
53
65
|
|
54
66
|
If you're planning to adopt Solid Queue incrementally by switching one job at the time, you can do so by leaving the `config.active_job.queue_adapter` set to your old backend, and then set the `queue_adapter` directly in the jobs you're moving:
|
@@ -61,22 +73,31 @@ class MyJob < ApplicationJob
|
|
61
73
|
# ...
|
62
74
|
end
|
63
75
|
```
|
76
|
+
|
64
77
|
## High performance requirements
|
65
78
|
|
66
79
|
Solid Queue was designed for the highest throughput when used 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. You can also use it with SQLite on smaller applications.
|
67
80
|
|
68
81
|
## Configuration
|
69
82
|
|
70
|
-
### Workers and
|
83
|
+
### Workers, dispatchers and scheduler
|
71
84
|
|
72
|
-
We have
|
85
|
+
We have several types of actors in Solid Queue:
|
73
86
|
- _Workers_ are in charge of picking jobs ready to run from queues and processing them. They work off the `solid_queue_ready_executions` table.
|
74
|
-
- _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.
|
87
|
+
- _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. On top of that, they do some maintenance work related to [concurrency controls](#concurrency-controls).
|
88
|
+
- The _scheduler_ manages [recurring tasks](#recurring-tasks), enqueuing jobs for them when they're due.
|
75
89
|
- The _supervisor_ runs workers and dispatchers according to the configuration, controls their heartbeats, and stops and starts them when needed.
|
76
90
|
|
77
|
-
Solid Queue's supervisor will fork a separate process for each supervised worker/dispatcher.
|
91
|
+
Solid Queue's supervisor will fork a separate process for each supervised worker/dispatcher/scheduler.
|
78
92
|
|
79
|
-
By default, Solid Queue will try to find your configuration under `config/
|
93
|
+
By default, Solid Queue will try to find your configuration under `config/queue.yml`, but you can set a different path using the environment variable `SOLID_QUEUE_CONFIG` or by using the `-c/--config_file` option with `bin/jobs`, like this:
|
94
|
+
|
95
|
+
```
|
96
|
+
bin/jobs -c config/calendar.yml
|
97
|
+
```
|
98
|
+
|
99
|
+
|
100
|
+
This is what this configuration looks like:
|
80
101
|
|
81
102
|
```yml
|
82
103
|
production:
|
@@ -105,6 +126,7 @@ production:
|
|
105
126
|
```
|
106
127
|
the supervisor will run 1 dispatcher and no workers.
|
107
128
|
|
129
|
+
|
108
130
|
Here's an overview of the different options:
|
109
131
|
|
110
132
|
- `polling_interval`: the time interval in seconds that workers and dispatchers will wait before checking for more jobs. This time defaults to `1` second for dispatchers and `0.1` seconds for workers.
|
@@ -127,7 +149,7 @@ Here's an overview of the different options:
|
|
127
149
|
- `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.
|
128
150
|
- `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.
|
129
151
|
- `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.
|
130
|
-
|
152
|
+
|
131
153
|
|
132
154
|
### Queue order and priorities
|
133
155
|
|
@@ -234,6 +256,9 @@ class MyJob < ApplicationJob
|
|
234
256
|
|
235
257
|
When a job includes these controls, we'll ensure that, at most, the number of jobs (indicated as `to`) that yield the same `key` will be performed concurrently, and this guarantee will last for `duration` for each job enqueued. Note that there's no guarantee about _the order of execution_, only about jobs being performed at the same time (overlapping).
|
236
258
|
|
259
|
+
The concurrency limits use the concept of semaphores when enqueuing, and work as follows: when a job is enqueued, we check if it specifies concurrency controls. If it does, we check the semaphore for the computed concurrency key. If the semaphore is open, we claim it and we set the job as _ready_. Ready means it can be picked up by workers for execution. When the job finishes executing (be it successfully or unsuccesfully, resulting in a failed execution), we signal the semaphore and try to unblock the next job with the same key, if any. Unblocking the next job doesn't mean running that job right away, but moving it from _blocked_ to _ready_. Since something can happen that prevents the first job from releasing the semaphore and unblocking the next job (for example, someone pulling a plug in the machine where the worker is running), we have the `duration` as a failsafe. Jobs that have been blocked for more than duration are candidates to be released, but only as many of them as the concurrency rules allow, as each one would need to go through the semaphore dance check. This means that the `duration` is not really about the job that's enqueued or being run, it's about the jobs that are blocked waiting.
|
260
|
+
|
261
|
+
|
237
262
|
For example:
|
238
263
|
```ruby
|
239
264
|
class DeliverAnnouncementToContactJob < ApplicationJob
|
@@ -242,7 +267,7 @@ class DeliverAnnouncementToContactJob < ApplicationJob
|
|
242
267
|
def perform(contact)
|
243
268
|
# ...
|
244
269
|
```
|
245
|
-
Where `contact` and `account` are `ActiveRecord` records. In this case, we'll ensure that at most two jobs of the kind `DeliverAnnouncementToContact` for the same account will run concurrently. If, for any reason, one of those jobs takes longer than 5 minutes or doesn't release its concurrency lock within 5 minutes of acquiring it, a new job with the same key might gain the lock.
|
270
|
+
Where `contact` and `account` are `ActiveRecord` records. In this case, we'll ensure that at most two jobs of the kind `DeliverAnnouncementToContact` for the same account will run concurrently. If, for any reason, one of those jobs takes longer than 5 minutes or doesn't release its concurrency lock (signals the semaphore) within 5 minutes of acquiring it, a new job with the same key might gain the lock.
|
246
271
|
|
247
272
|
Let's see another example using `group`:
|
248
273
|
|
@@ -268,7 +293,7 @@ Note that the `duration` setting depends indirectly on the value for `concurrenc
|
|
268
293
|
|
269
294
|
Jobs are unblocked in order of priority but queue order is not taken into account for unblocking jobs. That means that if you have a group of jobs that share a concurrency group but are in different queues, or jobs of the same class that you enqueue in different queues, the queue order you set for a worker is not taken into account when unblocking blocked ones. The reason is that a job that runs unblocks the next one, and the job itself doesn't know about a particular worker's queue order (you could even have different workers with different queue orders), it can only know about priority. Once blocked jobs are unblocked and available for polling, they'll be picked up by a worker following its queue order.
|
270
295
|
|
271
|
-
Finally, failed jobs that are automatically or manually retried work in the same way as new jobs that get enqueued: they get in the queue for
|
296
|
+
Finally, failed jobs that are automatically or manually retried work in the same way as new jobs that get enqueued: they get in the queue for getting an open semaphore, and whenever they get it, they'll be run. It doesn't matter if they had already gotten an open semaphore in the past.
|
272
297
|
|
273
298
|
## Failed jobs and retries
|
274
299
|
|
@@ -293,27 +318,42 @@ to your `puma.rb` configuration.
|
|
293
318
|
|
294
319
|
## Recurring tasks
|
295
320
|
|
296
|
-
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
|
321
|
+
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 the scheduler process and are defined in their own configuration file. By default, the file is located in `config/recurring.yml`, but you can set a different path using the environment variable `SOLID_QUEUE_RECURRING_SCHEDULE` or by using the `--recurring_schedule_file` option with `bin/jobs`, like this:
|
322
|
+
|
323
|
+
```
|
324
|
+
bin/jobs --recurring_schedule_file=config/schedule.yml
|
325
|
+
```
|
326
|
+
|
327
|
+
The configuration itself looks like this:
|
328
|
+
|
297
329
|
```yml
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
schedule: every second
|
330
|
+
a_periodic_job:
|
331
|
+
class: MyJob
|
332
|
+
args: [ 42, { status: "custom_status" } ]
|
333
|
+
schedule: every second
|
334
|
+
a_cleanup_task:
|
335
|
+
command: "DeletedStuff.clear_all"
|
336
|
+
schedule: every day at 9am
|
306
337
|
```
|
307
|
-
|
338
|
+
|
339
|
+
Tasks are specified as a hash/dictionary, where the key will be the task's key internally. Each task needs to either have a `class`, which will be the job class to enqueue, or a `command`, which will be eval'ed in the context of a job (`SolidQueue::RecurringJob`) that will be enqueued according to its schedule, in the `solid_queue_recurring` queue.
|
340
|
+
|
341
|
+
Each task needs to have also a schedule, which 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 optionally supply the following for each task:
|
342
|
+
- `args`: the 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.
|
308
343
|
|
309
344
|
The job in the example configuration above will be enqueued every second as:
|
310
345
|
```ruby
|
311
346
|
MyJob.perform_later(42, status: "custom_status")
|
312
347
|
```
|
313
348
|
|
314
|
-
|
349
|
+
- `queue`: a different queue to be used when enqueuing the job. If none, the queue set up for the job class.
|
350
|
+
|
351
|
+
- `priority`: a numeric priority value to be used when enqueuing the job.
|
352
|
+
|
315
353
|
|
316
|
-
|
354
|
+
Tasks are enqueued at their corresponding times by the scheduler, 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).
|
355
|
+
|
356
|
+
It's possible to run multiple schedulers with the same `recurring_tasks` configuration, for example, if you have multiple servers for redundancy, and you run the `scheduler` in more than one of them. 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.
|
317
357
|
|
318
358
|
Finally, it's possible to configure jobs that aren't handled by Solid Queue. That is, you can have a job like this in your app:
|
319
359
|
```ruby
|
@@ -328,13 +368,12 @@ end
|
|
328
368
|
|
329
369
|
You can still configure this in Solid Queue:
|
330
370
|
```yml
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
args: 22
|
336
|
-
schedule: "*/5 * * * *"
|
371
|
+
my_periodic_resque_job:
|
372
|
+
class: MyResqueJob
|
373
|
+
args: 22
|
374
|
+
schedule: "*/5 * * * *"
|
337
375
|
```
|
376
|
+
|
338
377
|
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.
|
339
378
|
|
340
379
|
## Inspiration
|
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/setup"
|
2
4
|
|
3
5
|
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
@@ -6,3 +8,14 @@ load "rails/tasks/engine.rake"
|
|
6
8
|
load "rails/tasks/statistics.rake"
|
7
9
|
|
8
10
|
require "bundler/gem_tasks"
|
11
|
+
|
12
|
+
def databases
|
13
|
+
%w[ mysql postgres sqlite ]
|
14
|
+
end
|
15
|
+
|
16
|
+
task :test do
|
17
|
+
databases.each do |database|
|
18
|
+
sh("TARGET_DB=#{database} bin/setup")
|
19
|
+
sh("TARGET_DB=#{database} bin/rails test")
|
20
|
+
end
|
21
|
+
end
|
data/UPGRADING.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# Upgrading to version 0.9.x
|
2
|
+
This version has two breaking changes regarding configuration:
|
3
|
+
- The default configuration file has changed from `config/solid_queue.yml` to `config/queue.yml`.
|
4
|
+
- Recurring tasks are now defined in `config/recurring.yml` (by default). Before, they would be defined as part of the _dispatcher_ configuration. Now they've been upgraded to their own configuration file, and a dedicated process (the _scheduler_) to manage them. Check the _Recurring tasks_ section in the `README` to learn how to configure them in detail. They still follow the same format as before when they lived under `dispatchers > recurring_tasks`.
|
5
|
+
|
1
6
|
# Upgrading to version 0.8.x
|
2
7
|
*IMPORTANT*: This version collapsed all migrations into a single `db/queue_schema.rb`, that will use a separate `queue` database. If you're upgrading from a version < 0.6.0, you need to upgrade to 0.6.0 first, ensure all migrations are up-to-date, and then upgrade further.
|
3
8
|
|
@@ -10,9 +10,9 @@ module SolidQueue
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class_methods do
|
13
|
-
def prune
|
13
|
+
def prune(excluding: nil)
|
14
14
|
SolidQueue.instrument :prune_processes, size: 0 do |payload|
|
15
|
-
prunable.non_blocking_lock.find_in_batches(batch_size: 50) do |batch|
|
15
|
+
prunable.excluding(excluding).non_blocking_lock.find_in_batches(batch_size: 50) do |batch|
|
16
16
|
payload[:size] += batch.size
|
17
17
|
|
18
18
|
batch.each(&:prune)
|
@@ -7,17 +7,30 @@ module SolidQueue
|
|
7
7
|
serialize :arguments, coder: Arguments, default: []
|
8
8
|
|
9
9
|
validate :supported_schedule
|
10
|
+
validate :ensure_command_or_class_present
|
10
11
|
validate :existing_job_class
|
11
12
|
|
12
13
|
scope :static, -> { where(static: true) }
|
13
14
|
|
15
|
+
mattr_accessor :default_job_class
|
16
|
+
self.default_job_class = RecurringJob
|
17
|
+
|
14
18
|
class << self
|
15
19
|
def wrap(args)
|
16
20
|
args.is_a?(self) ? args : from_configuration(args.first, **args.second)
|
17
21
|
end
|
18
22
|
|
19
23
|
def from_configuration(key, **options)
|
20
|
-
new
|
24
|
+
new \
|
25
|
+
key: key,
|
26
|
+
class_name: options[:class],
|
27
|
+
command: options[:command],
|
28
|
+
arguments: options[:args],
|
29
|
+
schedule: options[:schedule],
|
30
|
+
queue_name: options[:queue].presence,
|
31
|
+
priority: options[:priority].presence,
|
32
|
+
description: options[:description],
|
33
|
+
static: true
|
21
34
|
end
|
22
35
|
|
23
36
|
def create_or_update_all(tasks)
|
@@ -47,7 +60,7 @@ module SolidQueue
|
|
47
60
|
else
|
48
61
|
payload[:other_adapter] = true
|
49
62
|
|
50
|
-
perform_later do |job|
|
63
|
+
perform_later.tap do |job|
|
51
64
|
unless job.successfully_enqueued?
|
52
65
|
payload[:enqueue_error] = job.enqueue_error&.message
|
53
66
|
end
|
@@ -77,8 +90,14 @@ module SolidQueue
|
|
77
90
|
end
|
78
91
|
end
|
79
92
|
|
93
|
+
def ensure_command_or_class_present
|
94
|
+
unless command.present? || class_name.present?
|
95
|
+
errors.add :base, :command_and_class_blank, message: "either command or class_name must be present"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
80
99
|
def existing_job_class
|
81
|
-
|
100
|
+
if class_name.present? && job_class.nil?
|
82
101
|
errors.add :class_name, :undefined, message: "doesn't correspond to an existing class"
|
83
102
|
end
|
84
103
|
end
|
@@ -89,7 +108,7 @@ module SolidQueue
|
|
89
108
|
|
90
109
|
def enqueue_and_record(run_at:)
|
91
110
|
RecurringExecution.record(key, run_at) do
|
92
|
-
job_class.new(*arguments_with_kwargs).tap do |active_job|
|
111
|
+
job_class.new(*arguments_with_kwargs).set(enqueue_options).tap do |active_job|
|
93
112
|
active_job.run_callbacks(:enqueue) do
|
94
113
|
Job.enqueue(active_job)
|
95
114
|
end
|
@@ -98,12 +117,16 @@ module SolidQueue
|
|
98
117
|
end
|
99
118
|
end
|
100
119
|
|
101
|
-
def perform_later
|
102
|
-
job_class.
|
120
|
+
def perform_later
|
121
|
+
job_class.new(*arguments_with_kwargs).tap do |active_job|
|
122
|
+
active_job.enqueue(enqueue_options)
|
123
|
+
end
|
103
124
|
end
|
104
125
|
|
105
126
|
def arguments_with_kwargs
|
106
|
-
if
|
127
|
+
if class_name.nil?
|
128
|
+
command
|
129
|
+
elsif arguments.last.is_a?(Hash)
|
107
130
|
arguments[0...-1] + [ Hash.ruby2_keywords_hash(arguments.last) ]
|
108
131
|
else
|
109
132
|
arguments
|
@@ -116,7 +139,11 @@ module SolidQueue
|
|
116
139
|
end
|
117
140
|
|
118
141
|
def job_class
|
119
|
-
@job_class ||= class_name
|
142
|
+
@job_class ||= class_name.present? ? class_name.safe_constantize : self.class.default_job_class
|
143
|
+
end
|
144
|
+
|
145
|
+
def enqueue_options
|
146
|
+
{ queue: queue_name, priority: priority }.compact
|
120
147
|
end
|
121
148
|
end
|
122
149
|
end
|
@@ -4,7 +4,8 @@ class SolidQueue::InstallGenerator < Rails::Generators::Base
|
|
4
4
|
source_root File.expand_path("templates", __dir__)
|
5
5
|
|
6
6
|
def copy_files
|
7
|
-
template "config/
|
7
|
+
template "config/queue.yml"
|
8
|
+
template "config/recurring.yml"
|
8
9
|
template "db/queue_schema.rb"
|
9
10
|
template "bin/jobs"
|
10
11
|
chmod "bin/jobs", 0755 & ~File.umask, verbose: false
|
data/lib/solid_queue/cli.rb
CHANGED
@@ -4,7 +4,17 @@ require "thor"
|
|
4
4
|
|
5
5
|
module SolidQueue
|
6
6
|
class Cli < Thor
|
7
|
-
class_option :config_file, type: :string, aliases: "-c",
|
7
|
+
class_option :config_file, type: :string, aliases: "-c",
|
8
|
+
default: Configuration::DEFAULT_CONFIG_FILE_PATH,
|
9
|
+
desc: "Path to config file",
|
10
|
+
banner: "SOLID_QUEUE_CONFIG"
|
11
|
+
|
12
|
+
class_option :recurring_schedule_file, type: :string,
|
13
|
+
default: Configuration::DEFAULT_RECURRING_SCHEDULE_FILE_PATH,
|
14
|
+
desc: "Path to recurring schedule definition",
|
15
|
+
banner: "SOLID_QUEUE_RECURRING_SCHEDULE"
|
16
|
+
|
17
|
+
class_option :skip_recurring, type: :boolean, default: false
|
8
18
|
|
9
19
|
def self.exit_on_failure?
|
10
20
|
true
|
@@ -14,7 +24,7 @@ module SolidQueue
|
|
14
24
|
default_command :start
|
15
25
|
|
16
26
|
def start
|
17
|
-
SolidQueue::Supervisor.start(
|
27
|
+
SolidQueue::Supervisor.start(**options.symbolize_keys)
|
18
28
|
end
|
19
29
|
end
|
20
30
|
end
|
@@ -19,21 +19,21 @@ module SolidQueue
|
|
19
19
|
batch_size: 500,
|
20
20
|
polling_interval: 1,
|
21
21
|
concurrency_maintenance: true,
|
22
|
-
concurrency_maintenance_interval: 600
|
23
|
-
recurring_tasks: []
|
22
|
+
concurrency_maintenance_interval: 600
|
24
23
|
}
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
dispatchers: [ DISPATCHER_DEFAULTS ]
|
29
|
-
}
|
25
|
+
DEFAULT_CONFIG_FILE_PATH = "config/queue.yml"
|
26
|
+
DEFAULT_RECURRING_SCHEDULE_FILE_PATH = "config/recurring.yml"
|
30
27
|
|
31
|
-
def initialize(
|
32
|
-
@
|
28
|
+
def initialize(**options)
|
29
|
+
@options = options.with_defaults(default_options)
|
33
30
|
end
|
34
31
|
|
35
32
|
def configured_processes
|
36
|
-
|
33
|
+
if only_work? then workers
|
34
|
+
else
|
35
|
+
dispatchers + workers + schedulers
|
36
|
+
end
|
37
37
|
end
|
38
38
|
|
39
39
|
def max_number_of_threads
|
@@ -42,9 +42,29 @@ module SolidQueue
|
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
45
|
-
attr_reader :
|
45
|
+
attr_reader :options
|
46
|
+
|
47
|
+
def default_options
|
48
|
+
{
|
49
|
+
config_file: Rails.root.join(ENV["SOLID_QUEUE_CONFIG"] || DEFAULT_CONFIG_FILE_PATH),
|
50
|
+
recurring_schedule_file: Rails.root.join(ENV["SOLID_QUEUE_RECURRING_SCHEDULE"] || DEFAULT_RECURRING_SCHEDULE_FILE_PATH),
|
51
|
+
only_work: false,
|
52
|
+
only_dispatch: false,
|
53
|
+
skip_recurring: false
|
54
|
+
}
|
55
|
+
end
|
46
56
|
|
47
|
-
|
57
|
+
def only_work?
|
58
|
+
options[:only_work]
|
59
|
+
end
|
60
|
+
|
61
|
+
def only_dispatch?
|
62
|
+
options[:only_dispatch]
|
63
|
+
end
|
64
|
+
|
65
|
+
def skip_recurring_tasks?
|
66
|
+
options[:skip_recurring] || only_work?
|
67
|
+
end
|
48
68
|
|
49
69
|
def workers
|
50
70
|
workers_options.flat_map do |worker_options|
|
@@ -55,41 +75,58 @@ module SolidQueue
|
|
55
75
|
|
56
76
|
def dispatchers
|
57
77
|
dispatchers_options.map do |dispatcher_options|
|
58
|
-
|
59
|
-
Process.new :dispatcher, dispatcher_options.merge(recurring_tasks: recurring_tasks).with_defaults(DISPATCHER_DEFAULTS)
|
78
|
+
Process.new :dispatcher, dispatcher_options.with_defaults(DISPATCHER_DEFAULTS)
|
60
79
|
end
|
61
80
|
end
|
62
81
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
DEFAULT_CONFIG
|
69
|
-
end
|
82
|
+
def schedulers
|
83
|
+
if !skip_recurring_tasks? && recurring_tasks.any?
|
84
|
+
[ Process.new(:scheduler, recurring_tasks: recurring_tasks) ]
|
85
|
+
else
|
86
|
+
[]
|
70
87
|
end
|
71
88
|
end
|
72
89
|
|
73
90
|
def workers_options
|
74
|
-
@workers_options ||=
|
91
|
+
@workers_options ||= processes_config.fetch(:workers, [])
|
75
92
|
.map { |options| options.dup.symbolize_keys }
|
76
93
|
end
|
77
94
|
|
78
95
|
def dispatchers_options
|
79
|
-
@dispatchers_options ||=
|
96
|
+
@dispatchers_options ||= processes_config.fetch(:dispatchers, [])
|
80
97
|
.map { |options| options.dup.symbolize_keys }
|
81
98
|
end
|
82
99
|
|
83
|
-
def
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
def parse_recurring_tasks(tasks)
|
88
|
-
Array(tasks).map do |id, options|
|
100
|
+
def recurring_tasks
|
101
|
+
@recurring_tasks ||= recurring_tasks_config.map do |id, options|
|
89
102
|
RecurringTask.from_configuration(id, **options)
|
90
103
|
end.select(&:valid?)
|
91
104
|
end
|
92
105
|
|
106
|
+
def processes_config
|
107
|
+
@processes_config ||= config_from \
|
108
|
+
options.slice(:workers, :dispatchers).presence || options[:config_file],
|
109
|
+
keys: [ :workers, :dispatchers ],
|
110
|
+
fallback: { workers: [ WORKER_DEFAULTS ], dispatchers: [ DISPATCHER_DEFAULTS ] }
|
111
|
+
end
|
112
|
+
|
113
|
+
def recurring_tasks_config
|
114
|
+
@recurring_tasks ||= config_from options[:recurring_schedule_file]
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def config_from(file_or_hash, keys: [], fallback: {}, env: Rails.env)
|
119
|
+
load_config_from(file_or_hash).then do |config|
|
120
|
+
config = config[env.to_sym] ? config[env.to_sym] : config
|
121
|
+
config = config.slice(*keys) if keys.any? && config.present?
|
122
|
+
|
123
|
+
if config.empty? then fallback
|
124
|
+
else
|
125
|
+
config
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
93
130
|
def load_config_from(file_or_hash)
|
94
131
|
case file_or_hash
|
95
132
|
when Hash
|
@@ -97,29 +134,17 @@ module SolidQueue
|
|
97
134
|
when Pathname, String
|
98
135
|
load_config_from_file Pathname.new(file_or_hash)
|
99
136
|
when NilClass
|
100
|
-
|
137
|
+
{}
|
101
138
|
else
|
102
139
|
raise "Solid Queue cannot be initialized with #{file_or_hash.inspect}"
|
103
140
|
end
|
104
141
|
end
|
105
142
|
|
106
|
-
def load_config_from_env_location
|
107
|
-
if ENV["SOLID_QUEUE_CONFIG"].present?
|
108
|
-
load_config_from_file Rails.root.join(ENV["SOLID_QUEUE_CONFIG"])
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def load_config_from_default_location
|
113
|
-
Rails.root.join(DEFAULT_CONFIG_FILE_PATH).then do |config_file|
|
114
|
-
config_file.exist? ? load_config_from_file(config_file) : {}
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
143
|
def load_config_from_file(file)
|
119
144
|
if file.exist?
|
120
145
|
ActiveSupport::ConfigurationFile.parse(file).deep_symbolize_keys
|
121
146
|
else
|
122
|
-
|
147
|
+
{}
|
123
148
|
end
|
124
149
|
end
|
125
150
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Dispatcher < Processes::Poller
|
5
|
-
attr_accessor :batch_size, :concurrency_maintenance
|
5
|
+
attr_accessor :batch_size, :concurrency_maintenance
|
6
6
|
|
7
|
-
after_boot :start_concurrency_maintenance
|
8
|
-
before_shutdown :stop_concurrency_maintenance
|
7
|
+
after_boot :start_concurrency_maintenance
|
8
|
+
before_shutdown :stop_concurrency_maintenance
|
9
9
|
|
10
10
|
def initialize(**options)
|
11
11
|
options = options.dup.with_defaults(SolidQueue::Configuration::DISPATCHER_DEFAULTS)
|
@@ -13,13 +13,12 @@ module SolidQueue
|
|
13
13
|
@batch_size = options[:batch_size]
|
14
14
|
|
15
15
|
@concurrency_maintenance = ConcurrencyMaintenance.new(options[:concurrency_maintenance_interval], options[:batch_size]) if options[:concurrency_maintenance]
|
16
|
-
@recurring_schedule = RecurringSchedule.new(options[:recurring_tasks])
|
17
16
|
|
18
17
|
super(**options)
|
19
18
|
end
|
20
19
|
|
21
20
|
def metadata
|
22
|
-
super.merge(batch_size: batch_size, concurrency_maintenance_interval: concurrency_maintenance&.interval
|
21
|
+
super.merge(batch_size: batch_size, concurrency_maintenance_interval: concurrency_maintenance&.interval)
|
23
22
|
end
|
24
23
|
|
25
24
|
private
|
@@ -94,7 +94,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
94
94
|
if error = event.payload[:error]
|
95
95
|
warn formatted_event(event, action: "Error registering #{process_kind}", **attributes.merge(error: formatted_error(error)))
|
96
96
|
else
|
97
|
-
|
97
|
+
debug formatted_event(event, action: "Register #{process_kind}", **attributes)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
@@ -114,7 +114,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
114
114
|
if error = event.payload[:error]
|
115
115
|
warn formatted_event(event, action: "Error deregistering #{process.kind}", **attributes.merge(error: formatted_error(error)))
|
116
116
|
else
|
117
|
-
|
117
|
+
debug formatted_event(event, action: "Deregister #{process.kind}", **attributes)
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
@@ -10,6 +10,7 @@ module SolidQueue
|
|
10
10
|
|
11
11
|
def initialize(*)
|
12
12
|
@name = generate_name
|
13
|
+
@stopped = false
|
13
14
|
end
|
14
15
|
|
15
16
|
def kind
|
@@ -28,10 +29,18 @@ module SolidQueue
|
|
28
29
|
{}
|
29
30
|
end
|
30
31
|
|
32
|
+
def stop
|
33
|
+
@stopped = true
|
34
|
+
end
|
35
|
+
|
31
36
|
private
|
32
37
|
def generate_name
|
33
38
|
[ kind.downcase, SecureRandom.hex(10) ].join("-")
|
34
39
|
end
|
40
|
+
|
41
|
+
def stopped?
|
42
|
+
@stopped
|
43
|
+
end
|
35
44
|
end
|
36
45
|
end
|
37
46
|
end
|
@@ -29,11 +29,11 @@ module SolidQueue::Processes
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def deregister
|
32
|
-
process
|
32
|
+
process&.deregister
|
33
33
|
end
|
34
34
|
|
35
35
|
def registered?
|
36
|
-
process
|
36
|
+
process.present?
|
37
37
|
end
|
38
38
|
|
39
39
|
def launch_heartbeat
|
@@ -53,7 +53,10 @@ module SolidQueue::Processes
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def heartbeat
|
56
|
-
process.heartbeat
|
56
|
+
process.with_lock { process.heartbeat }
|
57
|
+
rescue ActiveRecord::RecordNotFound
|
58
|
+
self.process = nil
|
59
|
+
wake_up
|
57
60
|
end
|
58
61
|
end
|
59
62
|
end
|
@@ -17,7 +17,9 @@ module SolidQueue::Processes
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def stop
|
20
|
-
|
20
|
+
super
|
21
|
+
|
22
|
+
wake_up
|
21
23
|
@thread&.join
|
22
24
|
end
|
23
25
|
|
@@ -31,8 +33,6 @@ module SolidQueue::Processes
|
|
31
33
|
def boot
|
32
34
|
SolidQueue.instrument(:start_process, process: self) do
|
33
35
|
run_callbacks(:boot) do
|
34
|
-
@stopped = false
|
35
|
-
|
36
36
|
if running_as_fork?
|
37
37
|
register_signal_handlers
|
38
38
|
set_procline
|
@@ -41,18 +41,18 @@ module SolidQueue::Processes
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
def run
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
44
48
|
def shutting_down?
|
45
|
-
stopped? || (running_as_fork? && supervisor_went_away?) || finished?
|
49
|
+
stopped? || (running_as_fork? && supervisor_went_away?) || finished? || !registered?
|
46
50
|
end
|
47
51
|
|
48
52
|
def run
|
49
53
|
raise NotImplementedError
|
50
54
|
end
|
51
55
|
|
52
|
-
def stopped?
|
53
|
-
@stopped
|
54
|
-
end
|
55
|
-
|
56
56
|
def finished?
|
57
57
|
running_inline? && all_work_completed?
|
58
58
|
end
|
@@ -61,6 +61,9 @@ module SolidQueue::Processes
|
|
61
61
|
false
|
62
62
|
end
|
63
63
|
|
64
|
+
def shutdown
|
65
|
+
end
|
66
|
+
|
64
67
|
def set_procline
|
65
68
|
end
|
66
69
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Scheduler < Processes::Base
|
5
|
+
include Processes::Runnable
|
6
|
+
|
7
|
+
attr_accessor :recurring_schedule
|
8
|
+
|
9
|
+
after_boot :schedule_recurring_tasks
|
10
|
+
before_shutdown :unschedule_recurring_tasks
|
11
|
+
|
12
|
+
def initialize(recurring_tasks:, **options)
|
13
|
+
@recurring_schedule = RecurringSchedule.new(recurring_tasks)
|
14
|
+
|
15
|
+
super(**options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def metadata
|
19
|
+
super.merge(recurring_schedule: recurring_schedule.task_keys.presence)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
SLEEP_INTERVAL = 60 # Right now it doesn't matter, can be set to 1 in the future for dynamic tasks
|
24
|
+
|
25
|
+
def run
|
26
|
+
loop do
|
27
|
+
break if shutting_down?
|
28
|
+
|
29
|
+
interruptible_sleep(SLEEP_INTERVAL)
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
SolidQueue.instrument(:shutdown_process, process: self) do
|
33
|
+
run_callbacks(:shutdown) { shutdown }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def schedule_recurring_tasks
|
38
|
+
recurring_schedule.schedule_tasks
|
39
|
+
end
|
40
|
+
|
41
|
+
def unschedule_recurring_tasks
|
42
|
+
recurring_schedule.unschedule_tasks
|
43
|
+
end
|
44
|
+
|
45
|
+
def all_work_completed?
|
46
|
+
recurring_schedule.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_procline
|
50
|
+
procline "scheduling #{recurring_schedule.task_keys.join(",")}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -6,9 +6,9 @@ module SolidQueue
|
|
6
6
|
include Maintenance, Signals, Pidfiled
|
7
7
|
|
8
8
|
class << self
|
9
|
-
def start(
|
9
|
+
def start(**options)
|
10
10
|
SolidQueue.supervisor = true
|
11
|
-
configuration = Configuration.new(
|
11
|
+
configuration = Configuration.new(**options)
|
12
12
|
|
13
13
|
if configuration.configured_processes.any?
|
14
14
|
new(configuration).tap(&:start)
|
@@ -37,7 +37,7 @@ module SolidQueue
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def stop
|
40
|
-
|
40
|
+
super
|
41
41
|
run_stop_hooks
|
42
42
|
end
|
43
43
|
|
@@ -47,7 +47,6 @@ module SolidQueue
|
|
47
47
|
def boot
|
48
48
|
SolidQueue.instrument(:start_process, process: self) do
|
49
49
|
run_callbacks(:boot) do
|
50
|
-
@stopped = false
|
51
50
|
sync_std_streams
|
52
51
|
end
|
53
52
|
end
|
@@ -87,10 +86,6 @@ module SolidQueue
|
|
87
86
|
forks[pid] = process_instance
|
88
87
|
end
|
89
88
|
|
90
|
-
def stopped?
|
91
|
-
@stopped
|
92
|
-
end
|
93
|
-
|
94
89
|
def set_procline
|
95
90
|
procline "supervising #{supervised_processes.join(", ")}"
|
96
91
|
end
|
data/lib/solid_queue/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.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-09-
|
11
|
+
date: 2024-09-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -203,6 +203,7 @@ files:
|
|
203
203
|
- README.md
|
204
204
|
- Rakefile
|
205
205
|
- UPGRADING.md
|
206
|
+
- app/jobs/solid_queue/recurring_job.rb
|
206
207
|
- app/models/solid_queue/blocked_execution.rb
|
207
208
|
- app/models/solid_queue/claimed_execution.rb
|
208
209
|
- app/models/solid_queue/execution.rb
|
@@ -235,7 +236,8 @@ files:
|
|
235
236
|
- lib/generators/solid_queue/install/USAGE
|
236
237
|
- lib/generators/solid_queue/install/install_generator.rb
|
237
238
|
- lib/generators/solid_queue/install/templates/bin/jobs
|
238
|
-
- lib/generators/solid_queue/install/templates/config/
|
239
|
+
- lib/generators/solid_queue/install/templates/config/queue.yml
|
240
|
+
- lib/generators/solid_queue/install/templates/config/recurring.yml
|
239
241
|
- lib/generators/solid_queue/install/templates/db/queue_schema.rb
|
240
242
|
- lib/puma/plugin/solid_queue.rb
|
241
243
|
- lib/solid_queue.rb
|
@@ -244,7 +246,6 @@ files:
|
|
244
246
|
- lib/solid_queue/configuration.rb
|
245
247
|
- lib/solid_queue/dispatcher.rb
|
246
248
|
- lib/solid_queue/dispatcher/concurrency_maintenance.rb
|
247
|
-
- lib/solid_queue/dispatcher/recurring_schedule.rb
|
248
249
|
- lib/solid_queue/engine.rb
|
249
250
|
- lib/solid_queue/lifecycle_hooks.rb
|
250
251
|
- lib/solid_queue/log_subscriber.rb
|
@@ -260,6 +261,8 @@ files:
|
|
260
261
|
- lib/solid_queue/processes/registrable.rb
|
261
262
|
- lib/solid_queue/processes/runnable.rb
|
262
263
|
- lib/solid_queue/processes/supervised.rb
|
264
|
+
- lib/solid_queue/scheduler.rb
|
265
|
+
- lib/solid_queue/scheduler/recurring_schedule.rb
|
263
266
|
- lib/solid_queue/supervisor.rb
|
264
267
|
- lib/solid_queue/supervisor/maintenance.rb
|
265
268
|
- lib/solid_queue/supervisor/pidfile.rb
|
@@ -276,11 +279,15 @@ metadata:
|
|
276
279
|
homepage_uri: https://github.com/rails/solid_queue
|
277
280
|
source_code_uri: https://github.com/rails/solid_queue
|
278
281
|
post_install_message: |
|
279
|
-
Upgrading to Solid Queue 0.
|
280
|
-
|
282
|
+
Upgrading to Solid Queue 0.9.0? There are some breaking changes about how recurring tasks are configured.
|
283
|
+
|
284
|
+
Upgrading to Solid Queue 0.8.0 from < 0.6.0? You need to upgrade to 0.6.0 first.
|
281
285
|
|
282
286
|
Upgrading to Solid Queue 0.4.x, 0.5.x, 0.6.x or 0.7.x? There are some breaking changes about how Solid Queue is started,
|
283
287
|
configuration and new migrations.
|
288
|
+
|
289
|
+
--> Check https://github.com/rails/solid_queue/blob/main/UPGRADING.md
|
290
|
+
for upgrade instructions.
|
284
291
|
rdoc_options: []
|
285
292
|
require_paths:
|
286
293
|
- lib
|
File without changes
|