solid_queue 1.0.0 → 1.1.1
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 +212 -35
- data/app/models/solid_queue/claimed_execution.rb +1 -0
- data/app/models/solid_queue/job/clearable.rb +2 -1
- data/app/models/solid_queue/queue.rb +13 -0
- data/app/models/solid_queue/queue_selector.rb +35 -5
- data/app/models/solid_queue/record.rb +1 -1
- data/app/models/solid_queue/recurring_task.rb +19 -1
- data/lib/generators/solid_queue/install/install_generator.rb +5 -3
- data/lib/solid_queue/app_executor.rb +1 -1
- data/lib/solid_queue/configuration.rb +52 -4
- data/lib/solid_queue/dispatcher.rb +2 -1
- data/lib/solid_queue/log_subscriber.rb +2 -1
- data/lib/solid_queue/processes/interruptible.rb +14 -18
- data/lib/solid_queue/processes/poller.rb +4 -4
- data/lib/solid_queue/processes/runnable.rb +0 -4
- data/lib/solid_queue/scheduler/recurring_schedule.rb +1 -0
- data/lib/solid_queue/supervisor/pidfile.rb +1 -1
- data/lib/solid_queue/supervisor.rb +2 -2
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue/worker.rb +2 -1
- metadata +35 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c36dbf886c5dd03f4dab75e6c28b671fbd24ce011589bbab25ef44f640686c96
|
|
4
|
+
data.tar.gz: de499841947c2a12fa43e0e6e11cf8e8e0789c73ed7290b16ecc32ccfc133283
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 07b6b3b7c8a24aafe97b0b02acebd8fb06f2171dbdb41d6d5c779b70422c633df10850fd8dd569e0fc06a99d8ce4a5ecef21a17f0675fb547c3d5495f142473b
|
|
7
|
+
data.tar.gz: 4b8e7fbebd2a03c2fa25d498146d469dda245c9a2a979bb604257e2519d101e6a5e36c85479c7d1b3cafbdb3fbad6d0b8d564061868445a85fa1a361cad0dd70
|
data/README.md
CHANGED
|
@@ -6,6 +6,31 @@ 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
|
+
- [Single database configuration](#single-database-configuration)
|
|
13
|
+
- [Incremental adoption](#incremental-adoption)
|
|
14
|
+
- [High performance requirements](#high-performance-requirements)
|
|
15
|
+
- [Configuration](#configuration)
|
|
16
|
+
- [Workers, dispatchers and scheduler](#workers-dispatchers-and-scheduler)
|
|
17
|
+
- [Queue order and priorities](#queue-order-and-priorities)
|
|
18
|
+
- [Queues specification and performance](#queues-specification-and-performance)
|
|
19
|
+
- [Threads, processes and signals](#threads-processes-and-signals)
|
|
20
|
+
- [Database configuration](#database-configuration)
|
|
21
|
+
- [Other configuration settings](#other-configuration-settings)
|
|
22
|
+
- [Lifecycle hooks](#lifecycle-hooks)
|
|
23
|
+
- [Errors when enqueuing](#errors-when-enqueuing)
|
|
24
|
+
- [Concurrency controls](#concurrency-controls)
|
|
25
|
+
- [Failed jobs and retries](#failed-jobs-and-retries)
|
|
26
|
+
- [Error reporting on jobs](#error-reporting-on-jobs)
|
|
27
|
+
- [Puma plugin](#puma-plugin)
|
|
28
|
+
- [Jobs and transactional integrity](#jobs-and-transactional-integrity)
|
|
29
|
+
- [Recurring tasks](#recurring-tasks)
|
|
30
|
+
- [Inspiration](#inspiration)
|
|
31
|
+
- [License](#license)
|
|
32
|
+
|
|
33
|
+
|
|
9
34
|
## Installation
|
|
10
35
|
|
|
11
36
|
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:
|
|
@@ -43,8 +68,6 @@ production:
|
|
|
43
68
|
migrations_paths: db/queue_migrate
|
|
44
69
|
```
|
|
45
70
|
|
|
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
71
|
Then run `db:prepare` in production to ensure the database is created and the schema is loaded.
|
|
49
72
|
|
|
50
73
|
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 +76,72 @@ For small projects, you can run Solid Queue on the same machine as your webserve
|
|
|
53
76
|
|
|
54
77
|
**Note**: future changes to the schema will come in the form of regular migrations.
|
|
55
78
|
|
|
79
|
+
### Usage in development and other non-production environments
|
|
80
|
+
|
|
81
|
+
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).
|
|
82
|
+
|
|
83
|
+
For example, if you're using SQLite in development, update `database.yml` as follows:
|
|
84
|
+
|
|
85
|
+
```diff
|
|
86
|
+
development:
|
|
87
|
+
primary:
|
|
88
|
+
<<: *default
|
|
89
|
+
database: storage/development.sqlite3
|
|
90
|
+
+ queue:
|
|
91
|
+
+ <<: *default
|
|
92
|
+
+ database: storage/development_queue.sqlite3
|
|
93
|
+
+ migrations_paths: db/queue_migrate
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Next, add the following to `development.rb`
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Use Solid Queue in Development.
|
|
100
|
+
config.active_job.queue_adapter = :solid_queue
|
|
101
|
+
config.solid_queue.connects_to = { database: { writing: :queue } }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Once you've added this, run `db:prepare` to create the Solid Queue database and load the schema.
|
|
105
|
+
|
|
106
|
+
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:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# You can either set the env var, or check for development
|
|
110
|
+
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] || Rails.env.development?
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
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:
|
|
114
|
+
```ruby
|
|
115
|
+
config.solid_queue.logger = ActiveSupport::Logger.new(STDOUT)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**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.
|
|
119
|
+
|
|
120
|
+
In `config/cable.yml`
|
|
121
|
+
|
|
122
|
+
```diff
|
|
123
|
+
development:
|
|
124
|
+
- adapter: async
|
|
125
|
+
+ adapter: solid_cable
|
|
126
|
+
+ connects_to:
|
|
127
|
+
+ database:
|
|
128
|
+
+ writing: cable
|
|
129
|
+
+ polling_interval: 0.1.seconds
|
|
130
|
+
+ message_retention: 1.day
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
In `config/database.yml`
|
|
134
|
+
|
|
135
|
+
```diff
|
|
136
|
+
development:
|
|
137
|
+
primary:
|
|
138
|
+
<<: *default
|
|
139
|
+
database: storage/development.sqlite3
|
|
140
|
+
+ cable:
|
|
141
|
+
+ <<: *default
|
|
142
|
+
+ database: storage/development_cable.sqlite3
|
|
143
|
+
+ migrations_paths: db/cable_migrate
|
|
144
|
+
```
|
|
56
145
|
|
|
57
146
|
### Single database configuration
|
|
58
147
|
|
|
@@ -64,7 +153,7 @@ Running Solid Queue in a separate database is recommended, but it's also possibl
|
|
|
64
153
|
|
|
65
154
|
You won't have multiple databases, so `database.yml` doesn't need to have primary and queue database.
|
|
66
155
|
|
|
67
|
-
|
|
156
|
+
### Incremental adoption
|
|
68
157
|
|
|
69
158
|
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
159
|
|
|
@@ -77,7 +166,7 @@ class MyJob < ApplicationJob
|
|
|
77
166
|
end
|
|
78
167
|
```
|
|
79
168
|
|
|
80
|
-
|
|
169
|
+
### High performance requirements
|
|
81
170
|
|
|
82
171
|
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
172
|
|
|
@@ -86,6 +175,7 @@ Solid Queue was designed for the highest throughput when used with MySQL 8+ or P
|
|
|
86
175
|
### Workers, dispatchers and scheduler
|
|
87
176
|
|
|
88
177
|
We have several types of actors in Solid Queue:
|
|
178
|
+
|
|
89
179
|
- _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
180
|
- _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
181
|
- The _scheduler_ manages [recurring tasks](#recurring-tasks), enqueuing jobs for them when they're due.
|
|
@@ -99,7 +189,6 @@ By default, Solid Queue will try to find your configuration under `config/queue.
|
|
|
99
189
|
bin/jobs -c config/calendar.yml
|
|
100
190
|
```
|
|
101
191
|
|
|
102
|
-
|
|
103
192
|
This is what this configuration looks like:
|
|
104
193
|
|
|
105
194
|
```yml
|
|
@@ -149,7 +238,11 @@ Here's an overview of the different options:
|
|
|
149
238
|
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.
|
|
150
239
|
|
|
151
240
|
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.
|
|
241
|
+
|
|
242
|
+
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).
|
|
243
|
+
|
|
152
244
|
- `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.
|
|
245
|
+
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.
|
|
153
246
|
- `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.
|
|
154
247
|
- `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.
|
|
155
248
|
|
|
@@ -164,6 +257,67 @@ This is useful when you run jobs with different importance or urgency in the sam
|
|
|
164
257
|
|
|
165
258
|
We recommend not mixing queue order with priorities but either choosing one or the other, as that will make job execution order more straightforward for you.
|
|
166
259
|
|
|
260
|
+
### Queues specification and performance
|
|
261
|
+
|
|
262
|
+
To keep polling performant and ensure a covering index is always used, Solid Queue only does two types of polling queries:
|
|
263
|
+
```sql
|
|
264
|
+
-- No filtering by queue
|
|
265
|
+
SELECT job_id
|
|
266
|
+
FROM solid_queue_ready_executions
|
|
267
|
+
ORDER BY priority ASC, job_id ASC
|
|
268
|
+
LIMIT ?
|
|
269
|
+
FOR UPDATE SKIP LOCKED;
|
|
270
|
+
|
|
271
|
+
-- Filtering by a single queue
|
|
272
|
+
SELECT job_id
|
|
273
|
+
FROM solid_queue_ready_executions
|
|
274
|
+
WHERE queue_name = ?
|
|
275
|
+
ORDER BY priority ASC, job_id ASC
|
|
276
|
+
LIMIT ?
|
|
277
|
+
FOR UPDATE SKIP LOCKED;
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The first one (no filtering by queue) is used when you specify
|
|
281
|
+
```yml
|
|
282
|
+
queues: *
|
|
283
|
+
```
|
|
284
|
+
and there aren't any queues paused, as we want to target all queues.
|
|
285
|
+
|
|
286
|
+
In other cases, we need to have a list of queues to filter by, in order, because we can only filter by a single queue at a time to ensure we use an index to sort. This means that if you specify your queues as:
|
|
287
|
+
```yml
|
|
288
|
+
queues: beta*
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
we'll need to get a list of all existing queues matching that prefix first, with a query that would look like this:
|
|
292
|
+
```sql
|
|
293
|
+
SELECT DISTINCT(queue_name)
|
|
294
|
+
FROM solid_queue_ready_executions
|
|
295
|
+
WHERE queue_name LIKE 'beta%';
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
This type of `DISTINCT` query on a column that's the leftmost column in an index can be performed very fast in MySQL thanks to a technique called [Loose Index Scan](https://dev.mysql.com/doc/refman/8.0/en/group-by-optimization.html#loose-index-scan). PostgreSQL and SQLite, however, don't implement this technique, which means that if your `solid_queue_ready_executions` table is very big because your queues get very deep, this query will get slow. Normally your `solid_queue_ready_executions` table will be small, but it can happen.
|
|
299
|
+
|
|
300
|
+
Similarly to using prefixes, the same will happen if you have paused queues, because we need to get a list of all queues with a query like
|
|
301
|
+
```sql
|
|
302
|
+
SELECT DISTINCT(queue_name)
|
|
303
|
+
FROM solid_queue_ready_executions
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
and then remove the paused ones. Pausing in general should be something rare, used in special circumstances, and for a short period of time. If you don't want to process jobs from a queue anymore, the best way to do that is to remove it from your list of queues.
|
|
307
|
+
|
|
308
|
+
💡 To sum up, **if you want to ensure optimal performance on polling**, the best way to do that is to always specify exact names for them, and not have any queues paused.
|
|
309
|
+
|
|
310
|
+
Do this:
|
|
311
|
+
|
|
312
|
+
```yml
|
|
313
|
+
queues: background, backend
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
instead of this:
|
|
317
|
+
```yml
|
|
318
|
+
queues: back*
|
|
319
|
+
```
|
|
320
|
+
|
|
167
321
|
|
|
168
322
|
### Threads, processes and signals
|
|
169
323
|
|
|
@@ -175,7 +329,9 @@ The supervisor is in charge of managing these processes, and it responds to the
|
|
|
175
329
|
|
|
176
330
|
When receiving a `QUIT` signal, if workers still have jobs in-flight, these will be returned to the queue when the processes are deregistered.
|
|
177
331
|
|
|
178
|
-
If processes have no chance of cleaning up before exiting (e.g. if someone pulls a cable somewhere), in-flight jobs might remain claimed by the processes executing them. Processes send heartbeats, and the supervisor checks and prunes processes with expired heartbeats
|
|
332
|
+
If processes have no chance of cleaning up before exiting (e.g. if someone pulls a cable somewhere), in-flight jobs might remain claimed by the processes executing them. Processes send heartbeats, and the supervisor checks and prunes processes with expired heartbeats. Jobs that were claimed by processes with an expired heartbeat will be marked as failed with a `SolidQueue::Processes::ProcessPrunedError`. You can configure both the frequency of heartbeats and the threshold to consider a process dead. See the section below for this.
|
|
333
|
+
|
|
334
|
+
In a similar way, if a worker is terminated in any other way not initiated by the above signals (e.g. a worker is sent a `KILL` signal), jobs in progress will be marked as failed so that they can be inspected, with a `SolidQueue::Processes::Process::ProcessExitError`. Sometimes a job in particular is responsible for this, for example, if it has a memory leak and you have a mechanism to kill processes over a certain memory threshold, so this will help identifying this kind of situation.
|
|
179
335
|
|
|
180
336
|
|
|
181
337
|
### Database configuration
|
|
@@ -184,6 +340,32 @@ You can configure the database used by Solid Queue via the `config.solid_queue.c
|
|
|
184
340
|
|
|
185
341
|
All the options available to Active Record for multiple databases can be used here.
|
|
186
342
|
|
|
343
|
+
### Other configuration settings
|
|
344
|
+
|
|
345
|
+
_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`
|
|
346
|
+
|
|
347
|
+
There are several settings that control how Solid Queue works that you can set as well:
|
|
348
|
+
- `logger`: the logger you want Solid Queue to use. Defaults to the app logger.
|
|
349
|
+
- `app_executor`: the [Rails executor](https://guides.rubyonrails.org/threading_and_code_execution.html#executor) used to wrap asynchronous operations, defaults to the app executor
|
|
350
|
+
- `on_thread_error`: custom lambda/Proc to call when there's an error within a Solid Queue thread that takes the exception raised as argument. Defaults to
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
-> (exception) { Rails.error.report(exception, handled: false) }
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**This is not used for errors raised within a job execution**. Errors happening in jobs are handled by Active Job's `retry_on` or `discard_on`, and ultimately will result in [failed jobs](#failed-jobs-and-retries). This is for errors happening within Solid Queue itself.
|
|
357
|
+
|
|
358
|
+
- `use_skip_locked`: whether to use `FOR UPDATE SKIP LOCKED` when performing locking reads. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential.
|
|
359
|
+
- `process_heartbeat_interval`: the heartbeat interval that all processes will follow—defaults to 60 seconds.
|
|
360
|
+
- `process_alive_threshold`: how long to wait until a process is considered dead after its last heartbeat—defaults to 5 minutes.
|
|
361
|
+
- `shutdown_timeout`: time the supervisor will wait since it sent the `TERM` signal to its supervised processes before sending a `QUIT` version to them requesting immediate termination—defaults to 5 seconds.
|
|
362
|
+
- `silence_polling`: whether to silence Active Record logs emitted when polling for both workers and dispatchers—defaults to `true`.
|
|
363
|
+
- `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.
|
|
364
|
+
- `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`.
|
|
365
|
+
- `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).
|
|
366
|
+
- `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.
|
|
367
|
+
|
|
368
|
+
|
|
187
369
|
## Lifecycle hooks
|
|
188
370
|
|
|
189
371
|
In Solid queue, you can hook into two different points in the supervisor's life:
|
|
@@ -211,30 +393,6 @@ SolidQueue.on_stop { stop_metrics_server }
|
|
|
211
393
|
|
|
212
394
|
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.
|
|
213
395
|
|
|
214
|
-
### Other configuration settings
|
|
215
|
-
|
|
216
|
-
_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`
|
|
217
|
-
|
|
218
|
-
There are several settings that control how Solid Queue works that you can set as well:
|
|
219
|
-
- `logger`: the logger you want Solid Queue to use. Defaults to the app logger.
|
|
220
|
-
- `app_executor`: the [Rails executor](https://guides.rubyonrails.org/threading_and_code_execution.html#executor) used to wrap asynchronous operations, defaults to the app executor
|
|
221
|
-
- `on_thread_error`: custom lambda/Proc to call when there's an error within a Solid Queue thread that takes the exception raised as argument. Defaults to
|
|
222
|
-
|
|
223
|
-
```ruby
|
|
224
|
-
-> (exception) { Rails.error.report(exception, handled: false) }
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
**This is not used for errors raised within a job execution**. Errors happening in jobs are handled by Active Job's `retry_on` or `discard_on`, and ultimately will result in [failed jobs](#failed-jobs-and-retries). This is for errors happening within Solid Queue itself.
|
|
228
|
-
|
|
229
|
-
- `use_skip_locked`: whether to use `FOR UPDATE SKIP LOCKED` when performing locking reads. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential.
|
|
230
|
-
- `process_heartbeat_interval`: the heartbeat interval that all processes will follow—defaults to 60 seconds.
|
|
231
|
-
- `process_alive_threshold`: how long to wait until a process is considered dead after its last heartbeat—defaults to 5 minutes.
|
|
232
|
-
- `shutdown_timeout`: time the supervisor will wait since it sent the `TERM` signal to its supervised processes before sending a `QUIT` version to them requesting immediate termination—defaults to 5 seconds.
|
|
233
|
-
- `silence_polling`: whether to silence Active Record logs emitted when polling for both workers and dispatchers—defaults to `true`.
|
|
234
|
-
- `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.
|
|
235
|
-
- `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`.
|
|
236
|
-
- `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`, but this will happen automatically in the near future.
|
|
237
|
-
- `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.
|
|
238
396
|
|
|
239
397
|
## Errors when enqueuing
|
|
240
398
|
|
|
@@ -346,15 +504,27 @@ plugin :solid_queue
|
|
|
346
504
|
```
|
|
347
505
|
to your `puma.rb` configuration.
|
|
348
506
|
|
|
507
|
+
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:
|
|
508
|
+
```ruby
|
|
509
|
+
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]
|
|
510
|
+
```
|
|
511
|
+
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.
|
|
512
|
+
|
|
349
513
|
|
|
350
514
|
## Jobs and transactional integrity
|
|
351
|
-
: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
|
|
515
|
+
: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.
|
|
516
|
+
|
|
517
|
+
Starting from Rails 8, an option which doesn't rely on this transactional integrity and which Active Job provides is to defer the enqueueing of a job inside an Active Record transaction until that transaction successfully commits. This option can be set via the [`enqueue_after_transaction_commit`](https://edgeapi.rubyonrails.org/classes/ActiveJob/Enqueuing.html#method-c-enqueue_after_transaction_commit) class method on the job level and is by default disabled. Either it can be enabled for individual jobs or for all jobs through `ApplicationJob`:
|
|
352
518
|
|
|
353
|
-
|
|
519
|
+
```ruby
|
|
520
|
+
class ApplicationJob < ActiveJob::Base
|
|
521
|
+
self.enqueue_after_transaction_commit = true
|
|
522
|
+
end
|
|
523
|
+
```
|
|
354
524
|
|
|
355
|
-
|
|
525
|
+
Using this option, you can also use Solid Queue in the same database as your app but not rely on transactional integrity.
|
|
356
526
|
|
|
357
|
-
If you set
|
|
527
|
+
If you don't set this option but still want to make sure you're not inadvertently on transactional integrity, you can make sure that:
|
|
358
528
|
- Your jobs relying on specific data are always enqueued on [`after_commit` callbacks](https://guides.rubyonrails.org/active_record_callbacks.html#after-commit-and-after-rollback) or otherwise from a place where you're certain that whatever data the job will use has been committed to the database before the job is enqueued.
|
|
359
529
|
- Or, you configure a different database for Solid Queue, even if it's the same as your app, ensuring that a different connection on the thread handling requests or running jobs for your app will be used to enqueue jobs. For example:
|
|
360
530
|
|
|
@@ -369,6 +539,7 @@ If you set that to `never` but still want to make sure you're not inadvertently
|
|
|
369
539
|
config.solid_queue.connects_to = { database: { writing: :primary, reading: :replica } }
|
|
370
540
|
```
|
|
371
541
|
|
|
542
|
+
|
|
372
543
|
## Recurring tasks
|
|
373
544
|
|
|
374
545
|
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:
|
|
@@ -404,9 +575,15 @@ MyJob.perform_later(42, status: "custom_status")
|
|
|
404
575
|
|
|
405
576
|
- `priority`: a numeric priority value to be used when enqueuing the job.
|
|
406
577
|
|
|
407
|
-
|
|
408
578
|
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).
|
|
409
579
|
|
|
580
|
+
For recurring tasks defined as a `command`, you can also change the job class that runs them as follows:
|
|
581
|
+
```ruby
|
|
582
|
+
Rails.application.config.after_initialize do # or to_prepare
|
|
583
|
+
SolidQueue::RecurringTask.default_job_class = MyRecurringCommandJob
|
|
584
|
+
end
|
|
585
|
+
```
|
|
586
|
+
|
|
410
587
|
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.
|
|
411
588
|
|
|
412
589
|
**Note**: a single recurring schedule is supported, so you can have multiple schedulers using the same schedule, but not multiple schedulers using different configurations.
|
|
@@ -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
|
|
@@ -40,6 +40,19 @@ module SolidQueue
|
|
|
40
40
|
@size ||= ReadyExecution.queued_as(name).count
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def latency
|
|
44
|
+
@latency ||= begin
|
|
45
|
+
now = Time.current
|
|
46
|
+
oldest_enqueued_at = ReadyExecution.queued_as(name).minimum(:created_at) || now
|
|
47
|
+
|
|
48
|
+
(now - oldest_enqueued_at).to_i
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def human_latency
|
|
53
|
+
ActiveSupport::Duration.build(latency).inspect
|
|
54
|
+
end
|
|
55
|
+
|
|
43
56
|
def ==(queue)
|
|
44
57
|
name == queue.name
|
|
45
58
|
end
|
|
@@ -34,7 +34,7 @@ module SolidQueue
|
|
|
34
34
|
def eligible_queues
|
|
35
35
|
if include_all_queues? then all_queues
|
|
36
36
|
else
|
|
37
|
-
exact_names + prefixed_names
|
|
37
|
+
in_raw_order(exact_names + prefixed_names)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -42,8 +42,12 @@ module SolidQueue
|
|
|
42
42
|
"*".in? raw_queues
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
def all_queues
|
|
46
|
+
relation.distinct(:queue_name).pluck(:queue_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
45
49
|
def exact_names
|
|
46
|
-
raw_queues.select { |queue|
|
|
50
|
+
raw_queues.select { |queue| exact_name?(queue) }
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def prefixed_names
|
|
@@ -54,15 +58,41 @@ module SolidQueue
|
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
def prefixes
|
|
57
|
-
@prefixes ||= raw_queues.select { |queue|
|
|
61
|
+
@prefixes ||= raw_queues.select { |queue| prefixed_name?(queue) }.map { |queue| queue.tr("*", "%") }
|
|
58
62
|
end
|
|
59
63
|
|
|
60
|
-
def
|
|
61
|
-
|
|
64
|
+
def exact_name?(queue)
|
|
65
|
+
!queue.include?("*")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def prefixed_name?(queue)
|
|
69
|
+
queue.ends_with?("*")
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
def paused_queues
|
|
65
73
|
@paused_queues ||= Pause.all.pluck(:queue_name)
|
|
66
74
|
end
|
|
75
|
+
|
|
76
|
+
def in_raw_order(queues)
|
|
77
|
+
# Only need to sort if we have prefixes and more than one queue name.
|
|
78
|
+
# Exact names are selected in the same order as they're found
|
|
79
|
+
if queues.one? || prefixes.empty?
|
|
80
|
+
queues
|
|
81
|
+
else
|
|
82
|
+
queues = queues.dup
|
|
83
|
+
raw_queues.flat_map { |raw_queue| delete_in_order(raw_queue, queues) }.compact
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def delete_in_order(raw_queue, queues)
|
|
88
|
+
if exact_name?(raw_queue)
|
|
89
|
+
queues.delete(raw_queue)
|
|
90
|
+
elsif prefixed_name?(raw_queue)
|
|
91
|
+
prefix = raw_queue.tr("*", "")
|
|
92
|
+
queues.select { |queue| queue.start_with?(prefix) }.tap do |matches|
|
|
93
|
+
queues -= matches
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
67
97
|
end
|
|
68
98
|
end
|
|
@@ -4,7 +4,7 @@ module SolidQueue
|
|
|
4
4
|
class Record < ActiveRecord::Base
|
|
5
5
|
self.abstract_class = true
|
|
6
6
|
|
|
7
|
-
connects_to
|
|
7
|
+
connects_to(**SolidQueue.connects_to) if SolidQueue.connects_to
|
|
8
8
|
|
|
9
9
|
def self.non_blocking_lock
|
|
10
10
|
if SolidQueue.use_skip_locked
|
|
@@ -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?
|
|
@@ -67,11 +81,15 @@ module SolidQueue
|
|
|
67
81
|
end
|
|
68
82
|
end
|
|
69
83
|
|
|
70
|
-
|
|
84
|
+
active_job.tap do |enqueued_job|
|
|
85
|
+
payload[:active_job_id] = enqueued_job.job_id
|
|
86
|
+
end
|
|
71
87
|
rescue RecurringExecution::AlreadyRecorded
|
|
72
88
|
payload[:skipped] = true
|
|
89
|
+
false
|
|
73
90
|
rescue Job::EnqueueError => error
|
|
74
91
|
payload[:enqueue_error] = error.message
|
|
92
|
+
false
|
|
75
93
|
end
|
|
76
94
|
end
|
|
77
95
|
|
|
@@ -11,9 +11,11 @@ class SolidQueue::InstallGenerator < Rails::Generators::Base
|
|
|
11
11
|
chmod "bin/jobs", 0755 & ~File.umask, verbose: false
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
def configure_adapter_and_database
|
|
15
|
+
pathname = Pathname(destination_root).join("config/environments/production.rb")
|
|
16
|
+
|
|
17
|
+
gsub_file pathname, /\n\s*config\.solid_queue\.connects_to\s+=.*\n/, "\n", verbose: false
|
|
18
|
+
gsub_file pathname, /(# )?config\.active_job\.queue_adapter\s+=.*\n/,
|
|
17
19
|
"config.active_job.queue_adapter = :solid_queue\n" +
|
|
18
20
|
" config.solid_queue.connects_to = { database: { writing: :queue } }\n"
|
|
19
21
|
end
|
|
@@ -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
|
|
@@ -100,7 +142,7 @@ module SolidQueue
|
|
|
100
142
|
def recurring_tasks
|
|
101
143
|
@recurring_tasks ||= recurring_tasks_config.map do |id, options|
|
|
102
144
|
RecurringTask.from_configuration(id, **options)
|
|
103
|
-
end
|
|
145
|
+
end
|
|
104
146
|
end
|
|
105
147
|
|
|
106
148
|
def processes_config
|
|
@@ -147,5 +189,11 @@ module SolidQueue
|
|
|
147
189
|
{}
|
|
148
190
|
end
|
|
149
191
|
end
|
|
192
|
+
|
|
193
|
+
def estimated_number_of_threads
|
|
194
|
+
# At most "threads" in each worker + 1 thread for the worker + 1 thread for the heartbeat task
|
|
195
|
+
thread_count = workers_options.map { |options| options.fetch(:threads, WORKER_DEFAULTS[:threads]) }.max
|
|
196
|
+
(thread_count || 1) + 2
|
|
197
|
+
end
|
|
150
198
|
end
|
|
151
199
|
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
|
|
@@ -7,31 +7,27 @@ module SolidQueue::Processes
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
private
|
|
10
|
-
SELF_PIPE_BLOCK_SIZE = 11
|
|
11
10
|
|
|
12
11
|
def interrupt
|
|
13
|
-
|
|
14
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
|
15
|
-
# Ignore writes that would block and retry
|
|
16
|
-
# if another signal arrived while writing
|
|
17
|
-
retry
|
|
12
|
+
queue << true
|
|
18
13
|
end
|
|
19
14
|
|
|
15
|
+
# Sleeps for 'time'. Can be interrupted asynchronously and return early via wake_up.
|
|
16
|
+
# @param time [Numeric] the time to sleep. 0 returns immediately.
|
|
17
|
+
# @return [true, nil]
|
|
18
|
+
# * returns `true` if an interrupt was requested via #wake_up between the
|
|
19
|
+
# last call to `interruptible_sleep` and now, resulting in an early return.
|
|
20
|
+
# * returns `nil` if it slept the full `time` and was not interrupted.
|
|
20
21
|
def interruptible_sleep(time)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# Invoking this from the main thread may result in significant slowdown.
|
|
23
|
+
# Utilizing asynchronous execution (Futures) addresses this performance issue.
|
|
24
|
+
Concurrent::Promises.future(time) do |timeout|
|
|
25
|
+
queue.pop(timeout:).tap { queue.clear }
|
|
26
|
+
end.value
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@self_pipe ||= create_self_pipe
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def create_self_pipe
|
|
33
|
-
reader, writer = IO.pipe
|
|
34
|
-
{ reader: reader, writer: writer }
|
|
29
|
+
def queue
|
|
30
|
+
@queue ||= Queue.new
|
|
35
31
|
end
|
|
36
32
|
end
|
|
37
33
|
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
|
|
@@ -10,10 +10,10 @@ module SolidQueue
|
|
|
10
10
|
SolidQueue.supervisor = true
|
|
11
11
|
configuration = Configuration.new(**options)
|
|
12
12
|
|
|
13
|
-
if configuration.
|
|
13
|
+
if configuration.valid?
|
|
14
14
|
new(configuration).tap(&:start)
|
|
15
15
|
else
|
|
16
|
-
abort "
|
|
16
|
+
abort configuration.errors.full_messages.join("\n") + "\nExiting..."
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
data/lib/solid_queue/version.rb
CHANGED
data/lib/solid_queue/worker.rb
CHANGED
|
@@ -7,6 +7,7 @@ module SolidQueue
|
|
|
7
7
|
after_boot :run_start_hooks
|
|
8
8
|
before_shutdown :run_stop_hooks
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
attr_accessor :queues, :pool
|
|
11
12
|
|
|
12
13
|
def initialize(**options)
|
|
@@ -29,7 +30,7 @@ module SolidQueue
|
|
|
29
30
|
pool.post(execution)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
pool.idle? ? polling_interval : 10.minutes
|
|
33
34
|
end
|
|
34
35
|
end
|
|
35
36
|
|
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.
|
|
4
|
+
version: 1.1.1
|
|
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-
|
|
11
|
+
date: 2024-12-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -98,16 +98,16 @@ dependencies:
|
|
|
98
98
|
name: debug
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
|
-
- - "
|
|
101
|
+
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '
|
|
103
|
+
version: '1.9'
|
|
104
104
|
type: :development
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
|
-
- - "
|
|
108
|
+
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '
|
|
110
|
+
version: '1.9'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
112
|
name: mocha
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -192,6 +192,34 @@ dependencies:
|
|
|
192
192
|
- - ">="
|
|
193
193
|
- !ruby/object:Gem::Version
|
|
194
194
|
version: '0'
|
|
195
|
+
- !ruby/object:Gem::Dependency
|
|
196
|
+
name: rdoc
|
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - ">="
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: '0'
|
|
202
|
+
type: :development
|
|
203
|
+
prerelease: false
|
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
205
|
+
requirements:
|
|
206
|
+
- - ">="
|
|
207
|
+
- !ruby/object:Gem::Version
|
|
208
|
+
version: '0'
|
|
209
|
+
- !ruby/object:Gem::Dependency
|
|
210
|
+
name: logger
|
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
|
212
|
+
requirements:
|
|
213
|
+
- - ">="
|
|
214
|
+
- !ruby/object:Gem::Version
|
|
215
|
+
version: '0'
|
|
216
|
+
type: :development
|
|
217
|
+
prerelease: false
|
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
219
|
+
requirements:
|
|
220
|
+
- - ">="
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '0'
|
|
195
223
|
description: Database-backed Active Job backend.
|
|
196
224
|
email:
|
|
197
225
|
- rosa@37signals.com
|
|
@@ -302,7 +330,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
302
330
|
- !ruby/object:Gem::Version
|
|
303
331
|
version: '0'
|
|
304
332
|
requirements: []
|
|
305
|
-
rubygems_version: 3.5.
|
|
333
|
+
rubygems_version: 3.5.16
|
|
306
334
|
signing_key:
|
|
307
335
|
specification_version: 4
|
|
308
336
|
summary: Database-backed Active Job backend.
|