solid_queue 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +134 -30
- data/app/models/solid_queue/job/clearable.rb +2 -1
- data/app/models/solid_queue/recurring_task.rb +14 -0
- 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 +9 -5
- data/lib/solid_queue/processes/poller.rb +4 -4
- 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 +2 -2
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
|
@@ -153,6 +242,7 @@ Here's an overview of the different options:
|
|
153
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).
|
154
243
|
|
155
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.
|
156
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.
|
157
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.
|
158
248
|
|
@@ -250,6 +340,32 @@ You can configure the database used by Solid Queue via the `config.solid_queue.c
|
|
250
340
|
|
251
341
|
All the options available to Active Record for multiple databases can be used here.
|
252
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
|
+
|
253
369
|
## Lifecycle hooks
|
254
370
|
|
255
371
|
In Solid queue, you can hook into two different points in the supervisor's life:
|
@@ -277,30 +393,6 @@ SolidQueue.on_stop { stop_metrics_server }
|
|
277
393
|
|
278
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.
|
279
395
|
|
280
|
-
### Other configuration settings
|
281
|
-
|
282
|
-
_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`
|
283
|
-
|
284
|
-
There are several settings that control how Solid Queue works that you can set as well:
|
285
|
-
- `logger`: the logger you want Solid Queue to use. Defaults to the app logger.
|
286
|
-
- `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
|
287
|
-
- `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
|
288
|
-
|
289
|
-
```ruby
|
290
|
-
-> (exception) { Rails.error.report(exception, handled: false) }
|
291
|
-
```
|
292
|
-
|
293
|
-
**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.
|
294
|
-
|
295
|
-
- `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.
|
296
|
-
- `process_heartbeat_interval`: the heartbeat interval that all processes will follow—defaults to 60 seconds.
|
297
|
-
- `process_alive_threshold`: how long to wait until a process is considered dead after its last heartbeat—defaults to 5 minutes.
|
298
|
-
- `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.
|
299
|
-
- `silence_polling`: whether to silence Active Record logs emitted when polling for both workers and dispatchers—defaults to `true`.
|
300
|
-
- `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
|
-
- `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`, but this will happen automatically in the near future.
|
303
|
-
- `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
396
|
|
305
397
|
## Errors when enqueuing
|
306
398
|
|
@@ -412,6 +504,12 @@ plugin :solid_queue
|
|
412
504
|
```
|
413
505
|
to your `puma.rb` configuration.
|
414
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
|
+
|
415
513
|
|
416
514
|
## Jobs and transactional integrity
|
417
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.
|
@@ -477,9 +575,15 @@ MyJob.perform_later(42, status: "custom_status")
|
|
477
575
|
|
478
576
|
- `priority`: a numeric priority value to be used when enqueuing the job.
|
479
577
|
|
480
|
-
|
481
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).
|
482
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
|
+
|
483
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.
|
484
588
|
|
485
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
|
@@ -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?
|
@@ -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
|
@@ -12,13 +12,17 @@ module SolidQueue::Processes
|
|
12
12
|
queue << true
|
13
13
|
end
|
14
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.
|
15
21
|
def interruptible_sleep(time)
|
16
|
-
# Invoking from the main thread
|
17
|
-
#
|
22
|
+
# Invoking this from the main thread may result in significant slowdown.
|
23
|
+
# Utilizing asynchronous execution (Futures) addresses this performance issue.
|
18
24
|
Concurrent::Promises.future(time) do |timeout|
|
19
|
-
|
20
|
-
queue.clear
|
21
|
-
end
|
25
|
+
queue.pop(timeout:).tap { queue.clear }
|
22
26
|
end.value
|
23
27
|
end
|
24
28
|
|
@@ -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.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-12-
|
11
|
+
date: 2024-12-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|