solid_queue 1.1.0 → 1.1.4
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 +162 -35
- data/app/models/solid_queue/claimed_execution.rb +1 -1
- data/app/models/solid_queue/job/clearable.rb +2 -1
- data/app/models/solid_queue/recurring_task.rb +14 -0
- data/app/models/solid_queue/scheduled_execution.rb +1 -1
- data/lib/solid_queue/app_executor.rb +1 -1
- data/lib/solid_queue/configuration.rb +56 -6
- data/lib/solid_queue/dispatcher.rb +10 -11
- data/lib/solid_queue/engine.rb +8 -0
- data/lib/solid_queue/lifecycle_hooks.rb +11 -2
- data/lib/solid_queue/log_subscriber.rb +2 -1
- data/lib/solid_queue/pool.rb +3 -7
- data/lib/solid_queue/processes/base.rb +1 -1
- data/lib/solid_queue/processes/interruptible.rb +13 -5
- data/lib/solid_queue/processes/og_interruptible.rb +39 -0
- data/lib/solid_queue/processes/poller.rb +4 -4
- data/lib/solid_queue/processes/process_pruned_error.rb +1 -1
- data/lib/solid_queue/scheduler.rb +5 -1
- data/lib/solid_queue/supervisor.rb +4 -2
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue/worker.rb +6 -3
- data/lib/solid_queue.rb +12 -6
- metadata +20 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 778d9495c79b9b8af00416fd1980250fe6c3213043cbfd31eba5eb5e2795ad13
|
4
|
+
data.tar.gz: 14f929171d65334300648a5f80e59b9f8245034119cfd436d37dae545210c97b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbbd8113e179c49ffcfe707aa38c96f31ac812822d380e7a6739daced30ee029046ec19cf2ea70c81e19c0f1a819412454f1f4eb1a4cfad7cf5fa8b0f88e3021
|
7
|
+
data.tar.gz: c324b1127bbad96191d3c2e268058dd1cec7c29cffe45a511b0899f3e028e86b7ad0cf0638c93100393316bde1e429ab28c828a381c0692b8558c100f296b6f6
|
data/README.md
CHANGED
@@ -6,6 +6,32 @@ Besides regular job enqueuing and processing, Solid Queue supports delayed jobs,
|
|
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's multi-threading.
|
8
8
|
|
9
|
+
## Table of contents
|
10
|
+
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Usage in development and other non-production environments](#usage-in-development-and-other-non-production-environments)
|
13
|
+
- [Single database configuration](#single-database-configuration)
|
14
|
+
- [Incremental adoption](#incremental-adoption)
|
15
|
+
- [High performance requirements](#high-performance-requirements)
|
16
|
+
- [Configuration](#configuration)
|
17
|
+
- [Workers, dispatchers and scheduler](#workers-dispatchers-and-scheduler)
|
18
|
+
- [Queue order and priorities](#queue-order-and-priorities)
|
19
|
+
- [Queues specification and performance](#queues-specification-and-performance)
|
20
|
+
- [Threads, processes and signals](#threads-processes-and-signals)
|
21
|
+
- [Database configuration](#database-configuration)
|
22
|
+
- [Other configuration settings](#other-configuration-settings)
|
23
|
+
- [Lifecycle hooks](#lifecycle-hooks)
|
24
|
+
- [Errors when enqueuing](#errors-when-enqueuing)
|
25
|
+
- [Concurrency controls](#concurrency-controls)
|
26
|
+
- [Failed jobs and retries](#failed-jobs-and-retries)
|
27
|
+
- [Error reporting on jobs](#error-reporting-on-jobs)
|
28
|
+
- [Puma plugin](#puma-plugin)
|
29
|
+
- [Jobs and transactional integrity](#jobs-and-transactional-integrity)
|
30
|
+
- [Recurring tasks](#recurring-tasks)
|
31
|
+
- [Inspiration](#inspiration)
|
32
|
+
- [License](#license)
|
33
|
+
|
34
|
+
|
9
35
|
## Installation
|
10
36
|
|
11
37
|
Solid Queue is configured by default in new Rails 8 applications. But if you're running an earlier version, you can add it manually following these steps:
|
@@ -13,6 +39,8 @@ Solid Queue is configured by default in new Rails 8 applications. But if you're
|
|
13
39
|
1. `bundle add solid_queue`
|
14
40
|
2. `bin/rails solid_queue:install`
|
15
41
|
|
42
|
+
(Note: The minimum supported version of Rails is 7.1 and Ruby is 3.1.6.)
|
43
|
+
|
16
44
|
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
45
|
|
18
46
|
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:
|
@@ -43,8 +71,6 @@ production:
|
|
43
71
|
migrations_paths: db/queue_migrate
|
44
72
|
```
|
45
73
|
|
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
|
-
|
48
74
|
Then run `db:prepare` in production to ensure the database is created and the schema is loaded.
|
49
75
|
|
50
76
|
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.
|
@@ -53,6 +79,72 @@ For small projects, you can run Solid Queue on the same machine as your webserve
|
|
53
79
|
|
54
80
|
**Note**: future changes to the schema will come in the form of regular migrations.
|
55
81
|
|
82
|
+
### Usage in development and other non-production environments
|
83
|
+
|
84
|
+
Calling `bin/rails solid_queue:install` will automatically add `config.solid_queue.connects_to = { database: { writing: :queue } }` to `config/environments/production.rb`. In order to use Solid Queue in other environments (such as development or staging), you'll need to add a similar configuration(s).
|
85
|
+
|
86
|
+
For example, if you're using SQLite in development, update `database.yml` as follows:
|
87
|
+
|
88
|
+
```diff
|
89
|
+
development:
|
90
|
+
+ primary:
|
91
|
+
<<: *default
|
92
|
+
database: storage/development.sqlite3
|
93
|
+
+ queue:
|
94
|
+
+ <<: *default
|
95
|
+
+ database: storage/development_queue.sqlite3
|
96
|
+
+ migrations_paths: db/queue_migrate
|
97
|
+
```
|
98
|
+
|
99
|
+
Next, add the following to `development.rb`
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# Use Solid Queue in Development.
|
103
|
+
config.active_job.queue_adapter = :solid_queue
|
104
|
+
config.solid_queue.connects_to = { database: { writing: :queue } }
|
105
|
+
```
|
106
|
+
|
107
|
+
Once you've added this, run `db:prepare` to create the Solid Queue database and load the schema.
|
108
|
+
|
109
|
+
Finally, in order for jobs to be processed, you'll need to have Solid Queue running. In Development, this can be done via [the Puma plugin](#puma-plugin) as well. In `puma.rb` update the following line:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
# You can either set the env var, or check for development
|
113
|
+
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] || Rails.env.development?
|
114
|
+
```
|
115
|
+
|
116
|
+
You can also just use `bin/jobs`, but in this case you might want to [set a different logger for Solid Queue](#other-configuration-settings) because the default logger will log to `log/development.log` and you won't see anything when you run `bin/jobs`. For example:
|
117
|
+
```ruby
|
118
|
+
config.solid_queue.logger = ActiveSupport::Logger.new(STDOUT)
|
119
|
+
```
|
120
|
+
|
121
|
+
**Note about Action Cable**: If you use Action Cable (or anything dependent on Action Cable, such as Turbo Streams), you will also need to update it to use a database.
|
122
|
+
|
123
|
+
In `config/cable.yml`
|
124
|
+
|
125
|
+
```diff
|
126
|
+
development:
|
127
|
+
- adapter: async
|
128
|
+
+ adapter: solid_cable
|
129
|
+
+ connects_to:
|
130
|
+
+ database:
|
131
|
+
+ writing: cable
|
132
|
+
+ polling_interval: 0.1.seconds
|
133
|
+
+ message_retention: 1.day
|
134
|
+
```
|
135
|
+
|
136
|
+
In `config/database.yml`
|
137
|
+
|
138
|
+
```diff
|
139
|
+
development:
|
140
|
+
primary:
|
141
|
+
<<: *default
|
142
|
+
database: storage/development.sqlite3
|
143
|
+
+ cable:
|
144
|
+
+ <<: *default
|
145
|
+
+ database: storage/development_cable.sqlite3
|
146
|
+
+ migrations_paths: db/cable_migrate
|
147
|
+
```
|
56
148
|
|
57
149
|
### Single database configuration
|
58
150
|
|
@@ -64,7 +156,7 @@ Running Solid Queue in a separate database is recommended, but it's also possibl
|
|
64
156
|
|
65
157
|
You won't have multiple databases, so `database.yml` doesn't need to have primary and queue database.
|
66
158
|
|
67
|
-
|
159
|
+
### Incremental adoption
|
68
160
|
|
69
161
|
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:
|
70
162
|
|
@@ -77,7 +169,7 @@ class MyJob < ApplicationJob
|
|
77
169
|
end
|
78
170
|
```
|
79
171
|
|
80
|
-
|
172
|
+
### High performance requirements
|
81
173
|
|
82
174
|
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.
|
83
175
|
|
@@ -86,6 +178,7 @@ Solid Queue was designed for the highest throughput when used with MySQL 8+ or P
|
|
86
178
|
### Workers, dispatchers and scheduler
|
87
179
|
|
88
180
|
We have several types of actors in Solid Queue:
|
181
|
+
|
89
182
|
- _Workers_ are in charge of picking jobs ready to run from queues and processing them. They work off the `solid_queue_ready_executions` table.
|
90
183
|
- _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).
|
91
184
|
- The _scheduler_ manages [recurring tasks](#recurring-tasks), enqueuing jobs for them when they're due.
|
@@ -99,7 +192,6 @@ By default, Solid Queue will try to find your configuration under `config/queue.
|
|
99
192
|
bin/jobs -c config/calendar.yml
|
100
193
|
```
|
101
194
|
|
102
|
-
|
103
195
|
This is what this configuration looks like:
|
104
196
|
|
105
197
|
```yml
|
@@ -153,6 +245,7 @@ Here's an overview of the different options:
|
|
153
245
|
Check the sections below on [how queue order behaves combined with priorities](#queue-order-and-priorities), and [how the way you specify the queues per worker might affect performance](#queues-specification-and-performance).
|
154
246
|
|
155
247
|
- `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.
|
248
|
+
It is recommended to set this value less than or equal to the queue database's connection pool size minus 2, as each worker thread uses one connection, and two additional connections are reserved for polling and heartbeat.
|
156
249
|
- `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.
|
157
250
|
- `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.
|
158
251
|
|
@@ -220,7 +313,7 @@ and then remove the paused ones. Pausing in general should be something rare, us
|
|
220
313
|
Do this:
|
221
314
|
|
222
315
|
```yml
|
223
|
-
queues: background, backend
|
316
|
+
queues: [ background, backend ]
|
224
317
|
```
|
225
318
|
|
226
319
|
instead of this:
|
@@ -250,33 +343,6 @@ You can configure the database used by Solid Queue via the `config.solid_queue.c
|
|
250
343
|
|
251
344
|
All the options available to Active Record for multiple databases can be used here.
|
252
345
|
|
253
|
-
## Lifecycle hooks
|
254
|
-
|
255
|
-
In Solid queue, you can hook into two different points in the supervisor's life:
|
256
|
-
- `start`: after the supervisor has finished booting and right before it forks workers and dispatchers.
|
257
|
-
- `stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown.
|
258
|
-
|
259
|
-
And into two different points in a worker's life:
|
260
|
-
- `worker_start`: after the worker has finished booting and right before it starts the polling loop.
|
261
|
-
- `worker_stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown (which is just `exit!`).
|
262
|
-
|
263
|
-
You can use the following methods with a block to do this:
|
264
|
-
```ruby
|
265
|
-
SolidQueue.on_start
|
266
|
-
SolidQueue.on_stop
|
267
|
-
|
268
|
-
SolidQueue.on_worker_start
|
269
|
-
SolidQueue.on_worker_stop
|
270
|
-
```
|
271
|
-
|
272
|
-
For example:
|
273
|
-
```ruby
|
274
|
-
SolidQueue.on_start { start_metrics_server }
|
275
|
-
SolidQueue.on_stop { stop_metrics_server }
|
276
|
-
```
|
277
|
-
|
278
|
-
These can be called several times to add multiple hooks, but it needs to happen before Solid Queue is started. An initializer would be a good place to do this.
|
279
|
-
|
280
346
|
### Other configuration settings
|
281
347
|
|
282
348
|
_Note_: The settings in this section should be set in your `config/application.rb` or your environment config like this: `config.solid_queue.silence_polling = true`
|
@@ -299,9 +365,58 @@ There are several settings that control how Solid Queue works that you can set a
|
|
299
365
|
- `silence_polling`: whether to silence Active Record logs emitted when polling for both workers and dispatchers—defaults to `true`.
|
300
366
|
- `supervisor_pidfile`: path to a pidfile that the supervisor will create when booting to prevent running more than one supervisor in the same host, or in case you want to use it for a health check. It's `nil` by default.
|
301
367
|
- `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`.
|
302
|
-
- `clear_finished_jobs_after`: period to keep finished jobs around, in case `preserve_finished_jobs` is true—defaults to 1 day. **Note:** Right now, there's no automatic cleanup of finished jobs. You'd need to do this by periodically invoking `SolidQueue::Job.clear_finished_in_batches`,
|
368
|
+
- `clear_finished_jobs_after`: period to keep finished jobs around, in case `preserve_finished_jobs` is true—defaults to 1 day. **Note:** Right now, there's no automatic cleanup of finished jobs. You'd need to do this by periodically invoking `SolidQueue::Job.clear_finished_in_batches`, which can be configured as [a recurring task](#recurring-tasks).
|
303
369
|
- `default_concurrency_control_period`: the value to be used as the default for the `duration` parameter in [concurrency controls](#concurrency-controls). It defaults to 3 minutes.
|
304
370
|
|
371
|
+
|
372
|
+
## Lifecycle hooks
|
373
|
+
|
374
|
+
In Solid queue, you can hook into two different points in the supervisor's life:
|
375
|
+
- `start`: after the supervisor has finished booting and right before it forks workers and dispatchers.
|
376
|
+
- `stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown.
|
377
|
+
|
378
|
+
And into two different points in the worker's, dispatcher's and scheduler's life:
|
379
|
+
- `(worker|dispatcher|scheduler)_start`: after the worker/dispatcher/scheduler has finished booting and right before it starts the polling loop or loading the recurring schedule.
|
380
|
+
- `(worker|dispatcher|scheduler)_stop`: after receiving a signal (`TERM`, `INT` or `QUIT`) and right before starting graceful or immediate shutdown (which is just `exit!`).
|
381
|
+
|
382
|
+
Each of these hooks has an instance of the supervisor/worker/dispatcher/scheduler yielded to the block so that you may read its configuration for logging or metrics reporting purposes.
|
383
|
+
|
384
|
+
You can use the following methods with a block to do this:
|
385
|
+
```ruby
|
386
|
+
SolidQueue.on_start
|
387
|
+
SolidQueue.on_stop
|
388
|
+
|
389
|
+
SolidQueue.on_worker_start
|
390
|
+
SolidQueue.on_worker_stop
|
391
|
+
|
392
|
+
SolidQueue.on_dispatcher_start
|
393
|
+
SolidQueue.on_dispatcher_stop
|
394
|
+
|
395
|
+
SolidQueue.on_scheduler_start
|
396
|
+
SolidQueue.on_scheduler_stop
|
397
|
+
```
|
398
|
+
|
399
|
+
For example:
|
400
|
+
```ruby
|
401
|
+
SolidQueue.on_start do |supervisor|
|
402
|
+
MyMetricsReporter.process_name = supervisor.name
|
403
|
+
|
404
|
+
start_metrics_server
|
405
|
+
end
|
406
|
+
|
407
|
+
SolidQueue.on_stop do |_supervisor|
|
408
|
+
stop_metrics_server
|
409
|
+
end
|
410
|
+
|
411
|
+
SolidQueue.on_worker_start do |worker|
|
412
|
+
MyMetricsReporter.process_name = worker.name
|
413
|
+
MyMetricsReporter.queues = worker.queues.join(',')
|
414
|
+
end
|
415
|
+
```
|
416
|
+
|
417
|
+
These can be called several times to add multiple hooks, but it needs to happen before Solid Queue is started. An initializer would be a good place to do this.
|
418
|
+
|
419
|
+
|
305
420
|
## Errors when enqueuing
|
306
421
|
|
307
422
|
Solid Queue will raise a `SolidQueue::Job::EnqueueError` for any Active Record errors that happen when enqueuing a job. The reason for not raising `ActiveJob::EnqueueError` is that this one gets handled by Active Job, causing `perform_later` to return `false` and set `job.enqueue_error`, yielding the job to a block that you need to pass to `perform_later`. This works very well for your own jobs, but makes failure very hard to handle for jobs enqueued by Rails or other gems, such as `Turbo::Streams::BroadcastJob` or `ActiveStorage::AnalyzeJob`, because you don't control the call to `perform_later` in that cases.
|
@@ -412,6 +527,12 @@ plugin :solid_queue
|
|
412
527
|
```
|
413
528
|
to your `puma.rb` configuration.
|
414
529
|
|
530
|
+
If you're using Puma in development but you don't want to use Solid Queue in development, make sure you avoid the plugin being used, for example using an environment variable like this:
|
531
|
+
```ruby
|
532
|
+
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]
|
533
|
+
```
|
534
|
+
that you set in production only. This is what Rails 8's default Puma config looks like. Otherwise, if you're using Puma in development but not Solid Queue, starting Puma would start also Solid Queue supervisor and it'll most likely fail because it won't be properly configured.
|
535
|
+
|
415
536
|
|
416
537
|
## Jobs and transactional integrity
|
417
538
|
:warning: Having your jobs in the same ACID-compliant database as your application data enables a powerful yet sharp tool: taking advantage of transactional integrity to ensure some action in your app is not committed unless your job is also committed and vice versa, and ensuring that your job won't be enqueued until the transaction within which you're enqueuing it is committed. This can be very powerful and useful, but it can also backfire if you base some of your logic on this behaviour, and in the future, you move to another active job backend, or if you simply move Solid Queue to its own database, and suddenly the behaviour changes under you. Because this can be quite tricky and many people shouldn't need to worry about it, by default Solid Queue is configured in a different database as the main app.
|
@@ -477,9 +598,15 @@ MyJob.perform_later(42, status: "custom_status")
|
|
477
598
|
|
478
599
|
- `priority`: a numeric priority value to be used when enqueuing the job.
|
479
600
|
|
480
|
-
|
481
601
|
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).
|
482
602
|
|
603
|
+
For recurring tasks defined as a `command`, you can also change the job class that runs them as follows:
|
604
|
+
```ruby
|
605
|
+
Rails.application.config.after_initialize do # or to_prepare
|
606
|
+
SolidQueue::RecurringTask.default_job_class = MyRecurringCommandJob
|
607
|
+
end
|
608
|
+
```
|
609
|
+
|
483
610
|
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.
|
484
611
|
|
485
612
|
**Note**: a single recurring schedule is supported, so you can have multiple schedulers using the same schedule, but not multiple schedulers using different configurations.
|
@@ -92,7 +92,7 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
92
92
|
|
93
93
|
private
|
94
94
|
def execute
|
95
|
-
ActiveJob::Base.execute(job.arguments)
|
95
|
+
ActiveJob::Base.execute(job.arguments.merge("provider_job_id" => job.id))
|
96
96
|
Result.new(true, nil)
|
97
97
|
rescue Exception => e
|
98
98
|
Result.new(false, e)
|
@@ -10,9 +10,10 @@ module SolidQueue
|
|
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, class_name: nil)
|
13
|
+
def clear_finished_in_batches(batch_size: 500, finished_before: SolidQueue.clear_finished_jobs_after.ago, class_name: nil, sleep_between_batches: 0)
|
14
14
|
loop do
|
15
15
|
records_deleted = clearable(finished_before: finished_before, class_name: class_name).limit(batch_size).delete_all
|
16
|
+
sleep(sleep_between_batches) if sleep_between_batches > 0
|
16
17
|
break if records_deleted == 0
|
17
18
|
end
|
18
19
|
end
|
@@ -12,6 +12,8 @@ module SolidQueue
|
|
12
12
|
|
13
13
|
scope :static, -> { where(static: true) }
|
14
14
|
|
15
|
+
has_many :recurring_executions, foreign_key: :task_key, primary_key: :key
|
16
|
+
|
15
17
|
mattr_accessor :default_job_class
|
16
18
|
self.default_job_class = RecurringJob
|
17
19
|
|
@@ -53,6 +55,18 @@ module SolidQueue
|
|
53
55
|
parsed_schedule.next_time.utc
|
54
56
|
end
|
55
57
|
|
58
|
+
def previous_time
|
59
|
+
parsed_schedule.previous_time.utc
|
60
|
+
end
|
61
|
+
|
62
|
+
def last_enqueued_time
|
63
|
+
if recurring_executions.loaded?
|
64
|
+
recurring_executions.map(&:run_at).max
|
65
|
+
else
|
66
|
+
recurring_executions.maximum(:run_at)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
56
70
|
def enqueue(at:)
|
57
71
|
SolidQueue.instrument(:enqueue_recurring_task, task: key, at: at) do |payload|
|
58
72
|
active_job = if using_solid_queue_adapter?
|
@@ -14,7 +14,7 @@ module SolidQueue
|
|
14
14
|
def dispatch_next_batch(batch_size)
|
15
15
|
transaction do
|
16
16
|
job_ids = next_batch(batch_size).non_blocking_lock.pluck(:job_id)
|
17
|
-
if job_ids.empty? then
|
17
|
+
if job_ids.empty? then 0
|
18
18
|
else
|
19
19
|
SolidQueue.instrument(:dispatch_scheduled, batch_size: batch_size) do |payload|
|
20
20
|
payload[:size] = dispatch_jobs(job_ids)
|
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Configuration
|
5
|
+
include ActiveModel::Model
|
6
|
+
|
7
|
+
validate :ensure_configured_processes
|
8
|
+
validate :ensure_valid_recurring_tasks
|
9
|
+
validate :ensure_correctly_sized_thread_pool
|
10
|
+
|
5
11
|
class Process < Struct.new(:kind, :attributes)
|
6
12
|
def instantiate
|
7
13
|
"SolidQueue::#{kind.to_s.titleize}".safe_constantize.new(**attributes)
|
@@ -36,14 +42,46 @@ module SolidQueue
|
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
45
|
+
def error_messages
|
46
|
+
if configured_processes.none?
|
47
|
+
"No workers or processed configured. Exiting..."
|
48
|
+
else
|
49
|
+
error_messages = invalid_tasks.map do |task|
|
50
|
+
all_messages = task.errors.full_messages.map { |msg| "\t#{msg}" }.join("\n")
|
51
|
+
"#{task.key}:\n#{all_messages}"
|
52
|
+
end
|
53
|
+
.join("\n")
|
54
|
+
|
55
|
+
"Invalid processes configured:\n#{error_messages}"
|
56
|
+
end
|
42
57
|
end
|
43
58
|
|
44
59
|
private
|
45
60
|
attr_reader :options
|
46
61
|
|
62
|
+
def ensure_configured_processes
|
63
|
+
unless configured_processes.any?
|
64
|
+
errors.add(:base, "No processes configured")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def ensure_valid_recurring_tasks
|
69
|
+
unless skip_recurring_tasks? || invalid_tasks.none?
|
70
|
+
error_messages = invalid_tasks.map do |task|
|
71
|
+
"- #{task.key}: #{task.errors.full_messages.join(", ")}"
|
72
|
+
end
|
73
|
+
|
74
|
+
errors.add(:base, "Invalid recurring tasks:\n#{error_messages.join("\n")}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def ensure_correctly_sized_thread_pool
|
79
|
+
if (db_pool_size = SolidQueue::Record.connection_pool&.size) && db_pool_size < estimated_number_of_threads
|
80
|
+
errors.add(:base, "Solid Queue is configured to use #{estimated_number_of_threads} threads but the " +
|
81
|
+
"database connection pool is #{db_pool_size}. Increase it in `config/database.yml`")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
47
85
|
def default_options
|
48
86
|
{
|
49
87
|
config_file: Rails.root.join(ENV["SOLID_QUEUE_CONFIG"] || DEFAULT_CONFIG_FILE_PATH),
|
@@ -54,6 +92,10 @@ module SolidQueue
|
|
54
92
|
}
|
55
93
|
end
|
56
94
|
|
95
|
+
def invalid_tasks
|
96
|
+
recurring_tasks.select(&:invalid?)
|
97
|
+
end
|
98
|
+
|
57
99
|
def only_work?
|
58
100
|
options[:only_work]
|
59
101
|
end
|
@@ -99,8 +141,8 @@ module SolidQueue
|
|
99
141
|
|
100
142
|
def recurring_tasks
|
101
143
|
@recurring_tasks ||= recurring_tasks_config.map do |id, options|
|
102
|
-
RecurringTask.from_configuration(id, **options)
|
103
|
-
end.
|
144
|
+
RecurringTask.from_configuration(id, **options) if options&.has_key?(:schedule)
|
145
|
+
end.compact
|
104
146
|
end
|
105
147
|
|
106
148
|
def processes_config
|
@@ -111,7 +153,9 @@ module SolidQueue
|
|
111
153
|
end
|
112
154
|
|
113
155
|
def recurring_tasks_config
|
114
|
-
@recurring_tasks_config ||=
|
156
|
+
@recurring_tasks_config ||= begin
|
157
|
+
config_from options[:recurring_schedule_file]
|
158
|
+
end
|
115
159
|
end
|
116
160
|
|
117
161
|
|
@@ -147,5 +191,11 @@ module SolidQueue
|
|
147
191
|
{}
|
148
192
|
end
|
149
193
|
end
|
194
|
+
|
195
|
+
def estimated_number_of_threads
|
196
|
+
# At most "threads" in each worker + 1 thread for the worker + 1 thread for the heartbeat task
|
197
|
+
thread_count = workers_options.map { |options| options.fetch(:threads, WORKER_DEFAULTS[:threads]) }.max
|
198
|
+
(thread_count || 1) + 2
|
199
|
+
end
|
150
200
|
end
|
151
201
|
end
|
@@ -2,10 +2,14 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Dispatcher < Processes::Poller
|
5
|
-
|
5
|
+
include LifecycleHooks
|
6
|
+
attr_reader :batch_size
|
6
7
|
|
8
|
+
after_boot :run_start_hooks
|
7
9
|
after_boot :start_concurrency_maintenance
|
8
10
|
before_shutdown :stop_concurrency_maintenance
|
11
|
+
before_shutdown :run_stop_hooks
|
12
|
+
after_shutdown :run_exit_hooks
|
9
13
|
|
10
14
|
def initialize(**options)
|
11
15
|
options = options.dup.with_defaults(SolidQueue::Configuration::DISPATCHER_DEFAULTS)
|
@@ -22,9 +26,12 @@ module SolidQueue
|
|
22
26
|
end
|
23
27
|
|
24
28
|
private
|
29
|
+
attr_reader :concurrency_maintenance
|
30
|
+
|
25
31
|
def poll
|
26
32
|
batch = dispatch_next_batch
|
27
|
-
|
33
|
+
|
34
|
+
batch.zero? ? polling_interval : 0.seconds
|
28
35
|
end
|
29
36
|
|
30
37
|
def dispatch_next_batch
|
@@ -37,20 +44,12 @@ module SolidQueue
|
|
37
44
|
concurrency_maintenance&.start
|
38
45
|
end
|
39
46
|
|
40
|
-
def schedule_recurring_tasks
|
41
|
-
recurring_schedule.schedule_tasks
|
42
|
-
end
|
43
|
-
|
44
47
|
def stop_concurrency_maintenance
|
45
48
|
concurrency_maintenance&.stop
|
46
49
|
end
|
47
50
|
|
48
|
-
def unschedule_recurring_tasks
|
49
|
-
recurring_schedule.unschedule_tasks
|
50
|
-
end
|
51
|
-
|
52
51
|
def all_work_completed?
|
53
|
-
SolidQueue::ScheduledExecution.none?
|
52
|
+
SolidQueue::ScheduledExecution.none?
|
54
53
|
end
|
55
54
|
|
56
55
|
def set_procline
|
data/lib/solid_queue/engine.rb
CHANGED
@@ -37,5 +37,13 @@ module SolidQueue
|
|
37
37
|
include ActiveJob::ConcurrencyControls
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
initializer "solid_queue.include_interruptible_concern" do
|
42
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.2")
|
43
|
+
SolidQueue::Processes::Base.include SolidQueue::Processes::Interruptible
|
44
|
+
else
|
45
|
+
SolidQueue::Processes::Base.include SolidQueue::Processes::OgInterruptible
|
46
|
+
end
|
47
|
+
end
|
40
48
|
end
|
41
49
|
end
|
@@ -5,7 +5,7 @@ module SolidQueue
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
mattr_reader :lifecycle_hooks, default: { start: [], stop: [] }
|
8
|
+
mattr_reader :lifecycle_hooks, default: { start: [], stop: [], exit: [] }
|
9
9
|
end
|
10
10
|
|
11
11
|
class_methods do
|
@@ -17,7 +17,12 @@ module SolidQueue
|
|
17
17
|
self.lifecycle_hooks[:stop] << block
|
18
18
|
end
|
19
19
|
|
20
|
+
def on_exit(&block)
|
21
|
+
self.lifecycle_hooks[:exit] << block
|
22
|
+
end
|
23
|
+
|
20
24
|
def clear_hooks
|
25
|
+
self.lifecycle_hooks[:exit] = []
|
21
26
|
self.lifecycle_hooks[:start] = []
|
22
27
|
self.lifecycle_hooks[:stop] = []
|
23
28
|
end
|
@@ -32,9 +37,13 @@ module SolidQueue
|
|
32
37
|
run_hooks_for :stop
|
33
38
|
end
|
34
39
|
|
40
|
+
def run_exit_hooks
|
41
|
+
run_hooks_for :exit
|
42
|
+
end
|
43
|
+
|
35
44
|
def run_hooks_for(event)
|
36
45
|
self.class.lifecycle_hooks.fetch(event, []).each do |block|
|
37
|
-
|
46
|
+
block.call(self)
|
38
47
|
rescue Exception => exception
|
39
48
|
handle_thread_error(exception)
|
40
49
|
end
|
@@ -145,6 +145,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
145
145
|
end
|
146
146
|
|
147
147
|
def replace_fork(event)
|
148
|
+
supervisor_pid = event.payload[:supervisor_pid]
|
148
149
|
status = event.payload[:status]
|
149
150
|
attributes = event.payload.slice(:pid).merge \
|
150
151
|
status: (status.exitstatus || "no exit status set"),
|
@@ -155,7 +156,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
155
156
|
|
156
157
|
if replaced_fork = event.payload[:fork]
|
157
158
|
info formatted_event(event, action: "Replaced terminated #{replaced_fork.kind}", **attributes.merge(hostname: replaced_fork.hostname, name: replaced_fork.name))
|
158
|
-
|
159
|
+
elsif supervisor_pid != 1 # Running Docker, possibly having some processes that have been reparented
|
159
160
|
warn formatted_event(event, action: "Tried to replace forked process but it had already died", **attributes)
|
160
161
|
end
|
161
162
|
end
|
data/lib/solid_queue/pool.rb
CHANGED
@@ -18,20 +18,16 @@ module SolidQueue
|
|
18
18
|
def post(execution)
|
19
19
|
available_threads.decrement
|
20
20
|
|
21
|
-
|
21
|
+
Concurrent::Promises.future_on(executor, execution) do |thread_execution|
|
22
22
|
wrap_in_app_executor do
|
23
23
|
thread_execution.perform
|
24
24
|
ensure
|
25
25
|
available_threads.increment
|
26
26
|
mutex.synchronize { on_idle.try(:call) if idle? }
|
27
27
|
end
|
28
|
+
end.on_rejection! do |e|
|
29
|
+
handle_thread_error(e)
|
28
30
|
end
|
29
|
-
|
30
|
-
future.add_observer do |_, _, error|
|
31
|
-
handle_thread_error(error) if error
|
32
|
-
end
|
33
|
-
|
34
|
-
future.execute
|
35
31
|
end
|
36
32
|
|
37
33
|
def idle_threads
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module SolidQueue::Processes
|
4
4
|
module Interruptible
|
5
|
+
include SolidQueue::AppExecutor
|
6
|
+
|
5
7
|
def wake_up
|
6
8
|
interrupt
|
7
9
|
end
|
@@ -12,14 +14,20 @@ module SolidQueue::Processes
|
|
12
14
|
queue << true
|
13
15
|
end
|
14
16
|
|
17
|
+
# Sleeps for 'time'. Can be interrupted asynchronously and return early via wake_up.
|
18
|
+
# @param time [Numeric, Duration] the time to sleep. 0 returns immediately.
|
15
19
|
def interruptible_sleep(time)
|
16
|
-
# Invoking from the main thread
|
17
|
-
#
|
20
|
+
# Invoking this from the main thread may result in significant slowdown.
|
21
|
+
# Utilizing asynchronous execution (Futures) addresses this performance issue.
|
18
22
|
Concurrent::Promises.future(time) do |timeout|
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
queue.clear unless queue.pop(timeout:).nil?
|
24
|
+
end.on_rejection! do |e|
|
25
|
+
wrapped_exception = RuntimeError.new("Interruptible#interruptible_sleep - #{e.class}: #{e.message}")
|
26
|
+
wrapped_exception.set_backtrace(e.backtrace)
|
27
|
+
handle_thread_error(wrapped_exception)
|
22
28
|
end.value
|
29
|
+
|
30
|
+
nil
|
23
31
|
end
|
24
32
|
|
25
33
|
def queue
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue::Processes
|
4
|
+
# The original implementation of Interruptible that works
|
5
|
+
# with Ruby 3.1 and earlier
|
6
|
+
module OgInterruptible
|
7
|
+
def wake_up
|
8
|
+
interrupt
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
SELF_PIPE_BLOCK_SIZE = 11
|
13
|
+
|
14
|
+
def interrupt
|
15
|
+
self_pipe[:writer].write_nonblock(".")
|
16
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
17
|
+
# Ignore writes that would block and retry
|
18
|
+
# if another signal arrived while writing
|
19
|
+
retry
|
20
|
+
end
|
21
|
+
|
22
|
+
def interruptible_sleep(time)
|
23
|
+
if time > 0 && self_pipe[:reader].wait_readable(time)
|
24
|
+
loop { self_pipe[:reader].read_nonblock(SELF_PIPE_BLOCK_SIZE) }
|
25
|
+
end
|
26
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
27
|
+
end
|
28
|
+
|
29
|
+
# Self-pipe for signal-handling (http://cr.yp.to/docs/selfpipe.html)
|
30
|
+
def self_pipe
|
31
|
+
@self_pipe ||= create_self_pipe
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_self_pipe
|
35
|
+
reader, writer = IO.pipe
|
36
|
+
{ reader: reader, writer: writer }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -25,11 +25,11 @@ module SolidQueue::Processes
|
|
25
25
|
loop do
|
26
26
|
break if shutting_down?
|
27
27
|
|
28
|
-
wrap_in_app_executor do
|
29
|
-
|
30
|
-
interruptible_sleep(polling_interval)
|
31
|
-
end
|
28
|
+
delay = wrap_in_app_executor do
|
29
|
+
poll
|
32
30
|
end
|
31
|
+
|
32
|
+
interruptible_sleep(delay)
|
33
33
|
end
|
34
34
|
ensure
|
35
35
|
SolidQueue.instrument(:shutdown_process, process: self) do
|
@@ -4,7 +4,7 @@ module SolidQueue
|
|
4
4
|
module Processes
|
5
5
|
class ProcessPrunedError < RuntimeError
|
6
6
|
def initialize(last_heartbeat_at)
|
7
|
-
super("Process was found dead and pruned (last heartbeat at: #{last_heartbeat_at}")
|
7
|
+
super("Process was found dead and pruned (last heartbeat at: #{last_heartbeat_at})")
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -3,11 +3,15 @@
|
|
3
3
|
module SolidQueue
|
4
4
|
class Scheduler < Processes::Base
|
5
5
|
include Processes::Runnable
|
6
|
+
include LifecycleHooks
|
6
7
|
|
7
|
-
|
8
|
+
attr_reader :recurring_schedule
|
8
9
|
|
10
|
+
after_boot :run_start_hooks
|
9
11
|
after_boot :schedule_recurring_tasks
|
10
12
|
before_shutdown :unschedule_recurring_tasks
|
13
|
+
before_shutdown :run_stop_hooks
|
14
|
+
after_shutdown :run_exit_hooks
|
11
15
|
|
12
16
|
def initialize(recurring_tasks:, **options)
|
13
17
|
@recurring_schedule = RecurringSchedule.new(recurring_tasks)
|
@@ -5,15 +5,17 @@ module SolidQueue
|
|
5
5
|
include LifecycleHooks
|
6
6
|
include Maintenance, Signals, Pidfiled
|
7
7
|
|
8
|
+
after_shutdown :run_exit_hooks
|
9
|
+
|
8
10
|
class << self
|
9
11
|
def start(**options)
|
10
12
|
SolidQueue.supervisor = true
|
11
13
|
configuration = Configuration.new(**options)
|
12
14
|
|
13
|
-
if configuration.
|
15
|
+
if configuration.valid?
|
14
16
|
new(configuration).tap(&:start)
|
15
17
|
else
|
16
|
-
abort "
|
18
|
+
abort configuration.errors.full_messages.join("\n") + "\nExiting..."
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/solid_queue/version.rb
CHANGED
data/lib/solid_queue/worker.rb
CHANGED
@@ -6,13 +6,16 @@ module SolidQueue
|
|
6
6
|
|
7
7
|
after_boot :run_start_hooks
|
8
8
|
before_shutdown :run_stop_hooks
|
9
|
+
after_shutdown :run_exit_hooks
|
9
10
|
|
10
|
-
|
11
|
+
attr_reader :queues, :pool
|
11
12
|
|
12
13
|
def initialize(**options)
|
13
14
|
options = options.dup.with_defaults(SolidQueue::Configuration::WORKER_DEFAULTS)
|
14
15
|
|
15
|
-
|
16
|
+
# Ensure that the queues array is deep frozen to prevent accidental modification
|
17
|
+
@queues = Array(options[:queues]).map(&:freeze).freeze
|
18
|
+
|
16
19
|
@pool = Pool.new(options[:threads], on_idle: -> { wake_up })
|
17
20
|
|
18
21
|
super(**options)
|
@@ -29,7 +32,7 @@ module SolidQueue
|
|
29
32
|
pool.post(execution)
|
30
33
|
end
|
31
34
|
|
32
|
-
|
35
|
+
pool.idle? ? polling_interval : 10.minutes
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
data/lib/solid_queue.rb
CHANGED
@@ -41,14 +41,20 @@ module SolidQueue
|
|
41
41
|
mattr_accessor :clear_finished_jobs_after, default: 1.day
|
42
42
|
mattr_accessor :default_concurrency_control_period, default: 3.minutes
|
43
43
|
|
44
|
-
delegate :on_start, :on_stop, to: Supervisor
|
44
|
+
delegate :on_start, :on_stop, :on_exit, to: Supervisor
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
[ Dispatcher, Scheduler, Worker ].each do |process|
|
47
|
+
define_singleton_method(:"on_#{process.name.demodulize.downcase}_start") do |&block|
|
48
|
+
process.on_start(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
define_singleton_method(:"on_#{process.name.demodulize.downcase}_stop") do |&block|
|
52
|
+
process.on_stop(&block)
|
53
|
+
end
|
49
54
|
|
50
|
-
|
51
|
-
|
55
|
+
define_singleton_method(:"on_#{process.name.demodulize.downcase}_exit") do |&block|
|
56
|
+
process.on_exit(&block)
|
57
|
+
end
|
52
58
|
end
|
53
59
|
|
54
60
|
def supervisor?
|
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: 1.1.
|
4
|
+
version: 1.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rosa Gutierrez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -220,6 +220,20 @@ dependencies:
|
|
220
220
|
- - ">="
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: zeitwerk
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - '='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: 2.6.0
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - '='
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: 2.6.0
|
223
237
|
description: Database-backed Active Job backend.
|
224
238
|
email:
|
225
239
|
- rosa@37signals.com
|
@@ -281,6 +295,7 @@ files:
|
|
281
295
|
- lib/solid_queue/processes/base.rb
|
282
296
|
- lib/solid_queue/processes/callbacks.rb
|
283
297
|
- lib/solid_queue/processes/interruptible.rb
|
298
|
+
- lib/solid_queue/processes/og_interruptible.rb
|
284
299
|
- lib/solid_queue/processes/poller.rb
|
285
300
|
- lib/solid_queue/processes/process_exit_error.rb
|
286
301
|
- lib/solid_queue/processes/process_missing_error.rb
|
@@ -307,15 +322,8 @@ metadata:
|
|
307
322
|
homepage_uri: https://github.com/rails/solid_queue
|
308
323
|
source_code_uri: https://github.com/rails/solid_queue
|
309
324
|
post_install_message: |
|
310
|
-
Upgrading
|
311
|
-
|
312
|
-
Upgrading to Solid Queue 0.8.0 from < 0.6.0? You need to upgrade to 0.6.0 first.
|
313
|
-
|
314
|
-
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,
|
315
|
-
configuration and new migrations.
|
316
|
-
|
317
|
-
--> Check https://github.com/rails/solid_queue/blob/main/UPGRADING.md
|
318
|
-
for upgrade instructions.
|
325
|
+
Upgrading from Solid Queue < 1.0? Check details on breaking changes and upgrade instructions
|
326
|
+
--> https://github.com/rails/solid_queue/blob/main/UPGRADING.md
|
319
327
|
rdoc_options: []
|
320
328
|
require_paths:
|
321
329
|
- lib
|
@@ -323,7 +331,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
323
331
|
requirements:
|
324
332
|
- - ">="
|
325
333
|
- !ruby/object:Gem::Version
|
326
|
-
version: '
|
334
|
+
version: '3.1'
|
327
335
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
328
336
|
requirements:
|
329
337
|
- - ">="
|