solid_queue 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -24
- data/UPGRADING.md +102 -0
- data/app/models/solid_queue/claimed_execution.rb +5 -4
- data/app/models/solid_queue/process/executor.rb +7 -1
- data/app/models/solid_queue/process/prunable.rb +1 -7
- data/app/models/solid_queue/process.rb +10 -1
- data/db/migrate/20240813160053_make_name_not_null.rb +1 -1
- data/lib/generators/solid_queue/install/USAGE +1 -0
- data/lib/generators/solid_queue/install/install_generator.rb +21 -7
- data/lib/generators/solid_queue/install/templates/jobs +6 -0
- data/lib/puma/plugin/solid_queue.rb +10 -32
- data/lib/solid_queue/cli.rb +20 -0
- data/lib/solid_queue/configuration.rb +19 -14
- data/lib/solid_queue/dispatcher.rb +1 -1
- data/lib/solid_queue/processes/poller.rb +6 -4
- data/lib/solid_queue/processes/process_exit_error.rb +20 -0
- data/lib/solid_queue/processes/process_missing_error.rb +9 -0
- data/lib/solid_queue/processes/process_pruned_error.rb +11 -0
- data/lib/solid_queue/supervisor/maintenance.rb +1 -7
- data/lib/solid_queue/supervisor.rb +121 -9
- data/lib/solid_queue/version.rb +1 -1
- metadata +25 -6
- data/lib/solid_queue/supervisor/async_supervisor.rb +0 -47
- data/lib/solid_queue/supervisor/fork_supervisor.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23836c755cda6dd9bec594148ad92ea19740da4125e9115d17c44bd2543d752c
|
4
|
+
data.tar.gz: 40d91182c13f155a86879f04abcf4232a50f3502f4038b5e9e07a3c15da8def2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8ac683f1a0e635762d0d93b9b74f712c5fc5465381ea51230448dc2f176de67aafb0ee7031ae9e9cfee73edcee7bd70352e78aefd49ae25ea71a7485af95aff
|
7
|
+
data.tar.gz: b02eac304a30fb8c7e9e2627446a31bb56d22740e863b1d2749838429416f886da78d19d2000b533e3672576387ee075b8efb3c330f6177b5cd76a27955cd433
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Solid Queue is a DB-based queuing backend for [Active Job](https://edgeguides.rubyonrails.org/active_job_basics.html), designed with simplicity and performance in mind.
|
4
4
|
|
5
|
-
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`).
|
5
|
+
Besides regular job enqueuing and processing, Solid Queue supports delayed jobs, concurrency controls, pausing queues, numeric priorities per job, priorities by queue order, and bulk enqueuing (`enqueue_all` for Active Job's `perform_all_later`).
|
6
6
|
|
7
7
|
Solid Queue can be used with SQL databases such as MySQL, PostgreSQL or SQLite, and it leverages the `FOR UPDATE SKIP LOCKED` clause, if available, to avoid blocking and waiting on locks when polling jobs. It relies on Active Job for retries, discarding, error handling, serialization, or delays, and it's compatible with Ruby on Rails multi-threading.
|
8
8
|
|
@@ -31,9 +31,9 @@ $ bin/rails generate solid_queue:install
|
|
31
31
|
|
32
32
|
This will set `solid_queue` as the Active Job's adapter in production, and will copy the required migration over to your app.
|
33
33
|
|
34
|
-
Alternatively, you can
|
34
|
+
Alternatively, you can skip setting the Active Job's adapter with:
|
35
35
|
```bash
|
36
|
-
$ bin/rails solid_queue:install
|
36
|
+
$ bin/rails generate solid_queue:install --skip_adapter
|
37
37
|
```
|
38
38
|
|
39
39
|
And set Solid Queue as your Active Job's queue backend manually, in your environment config:
|
@@ -42,7 +42,7 @@ And set Solid Queue as your Active Job's queue backend manually, in your environ
|
|
42
42
|
config.active_job.queue_adapter = :solid_queue
|
43
43
|
```
|
44
44
|
|
45
|
-
|
45
|
+
Or you can set only specific jobs to use Solid Queue as their backend if you're migrating from another adapter and want to move jobs progressively:
|
46
46
|
|
47
47
|
```ruby
|
48
48
|
# app/jobs/my_job.rb
|
@@ -59,14 +59,14 @@ Finally, you need to run the migrations:
|
|
59
59
|
$ bin/rails db:migrate
|
60
60
|
```
|
61
61
|
|
62
|
-
After this, you'll be ready to enqueue jobs using Solid Queue, but you need to start Solid Queue's supervisor to run them.
|
62
|
+
After this, you'll be ready to enqueue jobs using Solid Queue, but you need to start Solid Queue's supervisor to run them. You can use the provided binstub:`
|
63
63
|
```bash
|
64
|
-
$
|
64
|
+
$ bin/jobs
|
65
65
|
```
|
66
66
|
|
67
67
|
This will start processing jobs in all queues using the default configuration. See [below](#configuration) to learn more about configuring Solid Queue.
|
68
68
|
|
69
|
-
For small projects, you can run Solid Queue on the same machine as your webserver. When you're ready to scale, Solid Queue supports horizontal scaling out-of-the-box. You can run Solid Queue on a separate server from your webserver, or even run `
|
69
|
+
For small projects, you can run Solid Queue on the same machine as your webserver. When you're ready to scale, Solid Queue supports horizontal scaling out-of-the-box. You can run Solid Queue on a separate server from your webserver, or even run `bin/jobs` on multiple machines at the same time. Depending on the configuration, you can designate some machines to run only dispatchers or only workers. See the [configuration](#configuration) section for more details on this.
|
70
70
|
|
71
71
|
## Requirements
|
72
72
|
Besides Rails 7.1, Solid Queue works best with MySQL 8+ or PostgreSQL 9.5+, as they support `FOR UPDATE SKIP LOCKED`. You can use it with older versions, but in that case, you might run into lock waits if you run multiple workers for the same queue.
|
@@ -80,7 +80,7 @@ We have three types of actors in Solid Queue:
|
|
80
80
|
- _Dispatchers_ are in charge of selecting jobs scheduled to run in the future that are due and _dispatching_ them, which is simply moving them from the `solid_queue_scheduled_executions` table over to the `solid_queue_ready_executions` table so that workers can pick them up. They're also in charge of managing [recurring tasks](#recurring-tasks), dispatching jobs to process them according to their schedule. On top of that, they do some maintenance work related to [concurrency controls](#concurrency-controls).
|
81
81
|
- The _supervisor_ runs workers and dispatchers according to the configuration, controls their heartbeats, and stops and starts them when needed.
|
82
82
|
|
83
|
-
|
83
|
+
Solid Queue's supervisor will fork a separate process for each supervised worker/dispatcher.
|
84
84
|
|
85
85
|
By default, Solid Queue will try to find your configuration under `config/solid_queue.yml`, but you can set a different path using the environment variable `SOLID_QUEUE_CONFIG`. This is what this configuration looks like:
|
86
86
|
|
@@ -131,7 +131,7 @@ Here's an overview of the different options:
|
|
131
131
|
|
132
132
|
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.
|
133
133
|
- `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.
|
134
|
-
- `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.
|
134
|
+
- `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.
|
135
135
|
- `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.
|
136
136
|
- `recurring_tasks`: a list of recurring tasks the dispatcher will manage. Read more details about this one in the [Recurring tasks](#recurring-tasks) section.
|
137
137
|
|
@@ -194,13 +194,13 @@ development:
|
|
194
194
|
# ...
|
195
195
|
```
|
196
196
|
|
197
|
-
Install migrations and specify the dedicated database name with the `
|
197
|
+
Install migrations and specify the dedicated database name with the `--database` option. This will create the Solid Queue migration files in a separate directory, matching the value provided in `migrations_paths` in `config/database.yml`.
|
198
198
|
|
199
199
|
```bash
|
200
|
-
$ bin/rails solid_queue:install
|
200
|
+
$ bin/rails g solid_queue:install --database solid_queue
|
201
201
|
```
|
202
202
|
|
203
|
-
Note: If you've already run the solid queue install command (`bin/rails generate solid_queue:install`), the migration files will have already been generated under the primary database's `db/migrate/` directory. You can remove these files and keep the ones generated by the database-specific migration installation above.
|
203
|
+
Note: If you've already run the solid queue install command (`bin/rails generate solid_queue:install`) without a `--database` option, the migration files will have already been generated under the primary database's `db/migrate/` directory. You can remove these files and keep the ones generated by the database-specific migration installation above.
|
204
204
|
|
205
205
|
Finally, run the migrations:
|
206
206
|
|
@@ -305,18 +305,6 @@ plugin :solid_queue
|
|
305
305
|
```
|
306
306
|
to your `puma.rb` configuration.
|
307
307
|
|
308
|
-
### Running as a fork or asynchronously
|
309
|
-
|
310
|
-
By default, the Puma plugin will fork additional processes for each worker and dispatcher so that they run in different processes. This provides the best isolation and performance, but can have additional memory usage.
|
311
|
-
|
312
|
-
Alternatively, workers and dispatchers can be run within the same Puma process(s). To do so just configure the plugin as:
|
313
|
-
|
314
|
-
```ruby
|
315
|
-
plugin :solid_queue
|
316
|
-
solid_queue_mode :async
|
317
|
-
```
|
318
|
-
|
319
|
-
Note that in this case, the `processes` configuration option will be ignored.
|
320
308
|
|
321
309
|
## Jobs and transactional integrity
|
322
310
|
: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. 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.
|
data/UPGRADING.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Upgrading to version 0.7.x
|
2
|
+
|
3
|
+
This version removed the new async mode introduced in version 0.4.0 and introduced a new binstub that can be used to start Solid Queue's supervisor. It includes also a minor migration.
|
4
|
+
|
5
|
+
To install both the binstub `bin/jobs` and the migration, you can just run
|
6
|
+
```
|
7
|
+
bin/rails generate solid_queue:install
|
8
|
+
```
|
9
|
+
|
10
|
+
Or, if you're using a different database for Solid Queue:
|
11
|
+
|
12
|
+
```bash
|
13
|
+
$ bin/rails generate solid_queue:install --database <the_name_of_your_solid_queue_db>
|
14
|
+
```
|
15
|
+
|
16
|
+
|
17
|
+
# Upgrading to version 0.6.x
|
18
|
+
|
19
|
+
## New migration in 3 steps
|
20
|
+
This version adds two new migrations to modify the `solid_queue_processes` table. The goal of that migration is to add a new column that needs to be `NOT NULL`. This needs to be done with two migrations and the following steps to ensure it happens without downtime and with new processes being able to register just fine:
|
21
|
+
1. Run the first migration that adds the new column, nullable
|
22
|
+
2. Deploy the updated Solid Queue code that uses this column
|
23
|
+
2. Run the second migration. This migration does two things:
|
24
|
+
- Backfill existing rows that would have the column as NULL
|
25
|
+
- Make the column not nullable and add a new index
|
26
|
+
|
27
|
+
Besides, it adds another migration with no effects to the `solid_queue_recurring_tasks` table. This one can be run just fine whenever, as the column affected is not used.
|
28
|
+
|
29
|
+
To install the migrations:
|
30
|
+
```bash
|
31
|
+
$ bin/rails solid_queue:install:migrations
|
32
|
+
```
|
33
|
+
|
34
|
+
Or, if you're using a different database for Solid Queue:
|
35
|
+
|
36
|
+
```bash
|
37
|
+
$ bin/rails solid_queue:install:migrations DATABASE=<the_name_of_your_solid_queue_db>
|
38
|
+
```
|
39
|
+
|
40
|
+
And then follow the steps above, running first one, then deploying the code, then running the second one.
|
41
|
+
|
42
|
+
## New behaviour when workers are killed
|
43
|
+
From this version onwards, when a worker is killed and the supervisor can detect that, it'll fail in-progress jobs claimed by that worker. For this to work correctly, you need to run the above migration and ensure you restart any supervisors you'd have.
|
44
|
+
|
45
|
+
|
46
|
+
# Upgrading to version 0.5.x
|
47
|
+
This version includes a new migration to improve recurring tasks. To install it, just run:
|
48
|
+
|
49
|
+
```bash
|
50
|
+
$ bin/rails solid_queue:install:migrations
|
51
|
+
```
|
52
|
+
|
53
|
+
Or, if you're using a different database for Solid Queue:
|
54
|
+
|
55
|
+
```bash
|
56
|
+
$ bin/rails solid_queue:install:migrations DATABASE=<the_name_of_your_solid_queue_db>
|
57
|
+
```
|
58
|
+
|
59
|
+
And then run the migrations.
|
60
|
+
|
61
|
+
|
62
|
+
# Upgrading to version 0.4.x
|
63
|
+
This version introduced an _async_ mode (this mode has been removed in version 0.7.0) to run the supervisor and have all workers and dispatchers run as part of the same process as the supervisor, instead of separate, forked, processes. Together with this, we introduced some changes in how the supervisor is started. Prior this change, you could choose whether you wanted to run workers, dispatchers or both, by starting Solid Queue as `solid_queue:work` or `solid_queue:dispatch`. From version 0.4.0, the only option available is:
|
64
|
+
|
65
|
+
```
|
66
|
+
$ bundle exec rake solid_queue:start
|
67
|
+
```
|
68
|
+
Whether the supervisor starts workers, dispatchers or both will depend on your configuration. For example, if you don't configure any dispatchers, only workers will be started. That is, with this configuration:
|
69
|
+
|
70
|
+
```yml
|
71
|
+
production:
|
72
|
+
workers:
|
73
|
+
- queues: [ real_time, background ]
|
74
|
+
threads: 5
|
75
|
+
polling_interval: 0.1
|
76
|
+
processes: 3
|
77
|
+
```
|
78
|
+
the supervisor will run 3 workers, each one with 5 threads, and no supervisors. With this configuration:
|
79
|
+
```yml
|
80
|
+
production:
|
81
|
+
dispatchers:
|
82
|
+
- polling_interval: 1
|
83
|
+
batch_size: 500
|
84
|
+
concurrency_maintenance_interval: 300
|
85
|
+
```
|
86
|
+
the supervisor will run 1 dispatcher and no workers.
|
87
|
+
|
88
|
+
|
89
|
+
# Upgrading to version 0.3.x
|
90
|
+
This version introduced support for [recurring (cron-style) jobs](https://github.com/rails/solid_queue/blob/main/README.md#recurring-tasks), and it needs a new DB migration for it. To install it, just run:
|
91
|
+
|
92
|
+
```bash
|
93
|
+
$ bin/rails solid_queue:install:migrations
|
94
|
+
```
|
95
|
+
|
96
|
+
Or, if you're using a different database for Solid Queue:
|
97
|
+
|
98
|
+
```bash
|
99
|
+
$ bin/rails solid_queue:install:migrations DATABASE=<the_name_of_your_solid_queue_db>
|
100
|
+
```
|
101
|
+
|
102
|
+
And then run the migrations.
|
@@ -29,8 +29,9 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
29
29
|
def release_all
|
30
30
|
SolidQueue.instrument(:release_many_claimed) do |payload|
|
31
31
|
includes(:job).tap do |executions|
|
32
|
-
payload[:size] = executions.size
|
33
32
|
executions.each(&:release)
|
33
|
+
|
34
|
+
payload[:size] = executions.size
|
34
35
|
end
|
35
36
|
end
|
36
37
|
end
|
@@ -38,11 +39,11 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
38
39
|
def fail_all_with(error)
|
39
40
|
SolidQueue.instrument(:fail_many_claimed) do |payload|
|
40
41
|
includes(:job).tap do |executions|
|
41
|
-
|
42
|
+
executions.each { |execution| execution.failed_with(error) }
|
43
|
+
|
42
44
|
payload[:process_ids] = executions.map(&:process_id).uniq
|
43
45
|
payload[:job_ids] = executions.map(&:job_id).uniq
|
44
|
-
|
45
|
-
executions.each { |execution| execution.failed_with(error) }
|
46
|
+
payload[:size] = executions.size
|
46
47
|
end
|
47
48
|
end
|
48
49
|
end
|
@@ -8,7 +8,7 @@ module SolidQueue
|
|
8
8
|
included do
|
9
9
|
has_many :claimed_executions
|
10
10
|
|
11
|
-
after_destroy
|
11
|
+
after_destroy :release_all_claimed_executions
|
12
12
|
end
|
13
13
|
|
14
14
|
def fail_all_claimed_executions_with(error)
|
@@ -17,6 +17,12 @@ module SolidQueue
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
def release_all_claimed_executions
|
21
|
+
if claims_executions?
|
22
|
+
claimed_executions.release_all
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
20
26
|
private
|
21
27
|
def claims_executions?
|
22
28
|
kind == "Worker"
|
@@ -1,12 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SolidQueue
|
4
|
-
class ProcessPrunedError < RuntimeError
|
5
|
-
def initialize(last_heartbeat_at)
|
6
|
-
super("Process was found dead and pruned (last heartbeat at: #{last_heartbeat_at}")
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
4
|
class Process
|
11
5
|
module Prunable
|
12
6
|
extend ActiveSupport::Concern
|
@@ -28,7 +22,7 @@ module SolidQueue
|
|
28
22
|
end
|
29
23
|
|
30
24
|
def prune
|
31
|
-
error = ProcessPrunedError.new(last_heartbeat_at)
|
25
|
+
error = Processes::ProcessPrunedError.new(last_heartbeat_at)
|
32
26
|
fail_all_claimed_executions_with(error)
|
33
27
|
|
34
28
|
deregister(pruned: true)
|
@@ -4,7 +4,7 @@ class SolidQueue::Process < SolidQueue::Record
|
|
4
4
|
include Executor, Prunable
|
5
5
|
|
6
6
|
belongs_to :supervisor, class_name: "SolidQueue::Process", optional: true, inverse_of: :supervisees
|
7
|
-
has_many :supervisees, class_name: "SolidQueue::Process", inverse_of: :supervisor, foreign_key: :supervisor_id
|
7
|
+
has_many :supervisees, class_name: "SolidQueue::Process", inverse_of: :supervisor, foreign_key: :supervisor_id
|
8
8
|
|
9
9
|
store :metadata, coder: JSON
|
10
10
|
|
@@ -26,9 +26,18 @@ class SolidQueue::Process < SolidQueue::Record
|
|
26
26
|
def deregister(pruned: false)
|
27
27
|
SolidQueue.instrument :deregister_process, process: self, pruned: pruned do |payload|
|
28
28
|
destroy!
|
29
|
+
|
30
|
+
unless supervised? || pruned
|
31
|
+
supervisees.each(&:deregister)
|
32
|
+
end
|
29
33
|
rescue Exception => error
|
30
34
|
payload[:error] = error
|
31
35
|
raise
|
32
36
|
end
|
33
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def supervised?
|
41
|
+
supervisor_id.present?
|
42
|
+
end
|
34
43
|
end
|
@@ -11,6 +11,6 @@ class MakeNameNotNull < ActiveRecord::Migration[7.1]
|
|
11
11
|
|
12
12
|
def down
|
13
13
|
remove_index :solid_queue_processes, [ :name, :supervisor_id ]
|
14
|
-
change_column :solid_queue_processes, :name, :string, null:
|
14
|
+
change_column :solid_queue_processes, :name, :string, null: true
|
15
15
|
end
|
16
16
|
end
|
@@ -3,19 +3,33 @@
|
|
3
3
|
class SolidQueue::InstallGenerator < Rails::Generators::Base
|
4
4
|
source_root File.expand_path("templates", __dir__)
|
5
5
|
|
6
|
-
class_option :
|
6
|
+
class_option :skip_adapter, type: :boolean, default: nil, desc: "Skip setting Solid Queue as the Active Job's adapter"
|
7
|
+
class_option :database, type: :string, default: nil, desc: "The database to use for migrations, if different from the primary one."
|
7
8
|
|
8
9
|
def add_solid_queue
|
9
|
-
|
10
|
-
|
10
|
+
unless options[:skip_adapter]
|
11
|
+
if (env_config = Pathname(destination_root).join("config/environments/production.rb")).exist?
|
12
|
+
say "Setting solid_queue as Active Job's queue adapter"
|
13
|
+
gsub_file env_config, /(# )?config\.active_job\.queue_adapter\s+=.*/, "config.active_job.queue_adapter = :solid_queue"
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
|
-
|
17
|
+
if File.exist?("config/solid_queue.yml")
|
18
|
+
say "Skipping sample configuration as config/solid_queue.yml exists"
|
19
|
+
else
|
20
|
+
say "Copying sample configuration"
|
21
|
+
copy_file "config.yml", "config/solid_queue.yml"
|
22
|
+
end
|
23
|
+
|
24
|
+
say "Copying binstub"
|
25
|
+
copy_file "jobs", "bin/jobs"
|
26
|
+
chmod "bin/jobs", 0755 & ~File.umask, verbose: false
|
14
27
|
end
|
15
28
|
|
16
29
|
def create_migrations
|
17
|
-
|
18
|
-
|
19
|
-
|
30
|
+
say "Installing database migrations"
|
31
|
+
arguments = [ "FROM=solid_queue" ]
|
32
|
+
arguments << "DATABASE=#{options[:database]}" if options[:database].present?
|
33
|
+
rails_command "railties:install:migrations #{arguments.join(" ")}", inline: true
|
20
34
|
end
|
21
35
|
end
|
@@ -1,13 +1,5 @@
|
|
1
1
|
require "puma/plugin"
|
2
2
|
|
3
|
-
module Puma
|
4
|
-
class DSL
|
5
|
-
def solid_queue_mode(mode = :fork)
|
6
|
-
@options[:solid_queue_mode] = mode.to_sym
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
3
|
Puma::Plugin.create do
|
12
4
|
attr_reader :puma_pid, :solid_queue_pid, :log_writer, :solid_queue_supervisor
|
13
5
|
|
@@ -15,36 +7,22 @@ Puma::Plugin.create do
|
|
15
7
|
@log_writer = launcher.log_writer
|
16
8
|
@puma_pid = $$
|
17
9
|
|
18
|
-
|
19
|
-
|
20
|
-
else
|
21
|
-
start_forked(launcher)
|
10
|
+
in_background do
|
11
|
+
monitor_solid_queue
|
22
12
|
end
|
23
|
-
end
|
24
13
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
launcher.events.on_booted do
|
15
|
+
@solid_queue_pid = fork do
|
16
|
+
Thread.new { monitor_puma }
|
17
|
+
SolidQueue::Supervisor.start
|
29
18
|
end
|
30
|
-
|
31
|
-
launcher.events.on_booted do
|
32
|
-
@solid_queue_pid = fork do
|
33
|
-
Thread.new { monitor_puma }
|
34
|
-
SolidQueue::Supervisor.start(mode: :fork)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
launcher.events.on_stopped { stop_solid_queue }
|
39
|
-
launcher.events.on_restart { stop_solid_queue }
|
40
19
|
end
|
41
20
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
launcher.events.on_restart { solid_queue_supervisor.stop; solid_queue_supervisor.start }
|
46
|
-
end
|
21
|
+
launcher.events.on_stopped { stop_solid_queue }
|
22
|
+
launcher.events.on_restart { stop_solid_queue }
|
23
|
+
end
|
47
24
|
|
25
|
+
private
|
48
26
|
def stop_solid_queue
|
49
27
|
Process.waitpid(solid_queue_pid, Process::WNOHANG)
|
50
28
|
log "Stopping Solid Queue..."
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
module SolidQueue
|
6
|
+
class Cli < Thor
|
7
|
+
class_option :config_file, type: :string, aliases: "-c", default: Configuration::DEFAULT_CONFIG_FILE_PATH, desc: "Path to config file"
|
8
|
+
|
9
|
+
def self.exit_on_failure?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
desc :start, "Starts Solid Queue supervisor to dispatch and perform enqueued jobs. Default command."
|
14
|
+
default_command :start
|
15
|
+
|
16
|
+
def start
|
17
|
+
SolidQueue::Supervisor.start(load_configuration_from: options["config_file"])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -23,8 +23,12 @@ module SolidQueue
|
|
23
23
|
recurring_tasks: []
|
24
24
|
}
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
DEFAULT_CONFIG = {
|
27
|
+
workers: [ WORKER_DEFAULTS ],
|
28
|
+
dispatchers: [ DISPATCHER_DEFAULTS ]
|
29
|
+
}
|
30
|
+
|
31
|
+
def initialize(load_from: nil)
|
28
32
|
@raw_config = config_from(load_from)
|
29
33
|
end
|
30
34
|
|
@@ -38,17 +42,13 @@ module SolidQueue
|
|
38
42
|
end
|
39
43
|
|
40
44
|
private
|
41
|
-
attr_reader :raw_config
|
45
|
+
attr_reader :raw_config
|
42
46
|
|
43
47
|
DEFAULT_CONFIG_FILE_PATH = "config/solid_queue.yml"
|
44
48
|
|
45
49
|
def workers
|
46
50
|
workers_options.flat_map do |worker_options|
|
47
|
-
processes =
|
48
|
-
worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
|
49
|
-
else
|
50
|
-
WORKER_DEFAULTS[:processes]
|
51
|
-
end
|
51
|
+
processes = worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
|
52
52
|
processes.times.map { Process.new(:worker, worker_options.with_defaults(WORKER_DEFAULTS)) }
|
53
53
|
end
|
54
54
|
end
|
@@ -61,22 +61,27 @@ module SolidQueue
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def config_from(file_or_hash, env: Rails.env)
|
64
|
-
|
65
|
-
|
64
|
+
load_config_from(file_or_hash).then do |config|
|
65
|
+
config = config[env.to_sym] ? config[env.to_sym] : config
|
66
|
+
if (config.keys & DEFAULT_CONFIG.keys).any? then config
|
67
|
+
else
|
68
|
+
DEFAULT_CONFIG
|
69
|
+
end
|
70
|
+
end
|
66
71
|
end
|
67
72
|
|
68
73
|
def workers_options
|
69
|
-
@workers_options ||= options_from_raw_config(:workers
|
74
|
+
@workers_options ||= options_from_raw_config(:workers)
|
70
75
|
.map { |options| options.dup.symbolize_keys }
|
71
76
|
end
|
72
77
|
|
73
78
|
def dispatchers_options
|
74
|
-
@dispatchers_options ||= options_from_raw_config(:dispatchers
|
79
|
+
@dispatchers_options ||= options_from_raw_config(:dispatchers)
|
75
80
|
.map { |options| options.dup.symbolize_keys }
|
76
81
|
end
|
77
82
|
|
78
|
-
def options_from_raw_config(key
|
79
|
-
|
83
|
+
def options_from_raw_config(key)
|
84
|
+
Array(raw_config[key])
|
80
85
|
end
|
81
86
|
|
82
87
|
def parse_recurring_tasks(tasks)
|
@@ -45,10 +45,12 @@ module SolidQueue::Processes
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def with_polling_volume
|
48
|
-
|
49
|
-
ActiveRecord::Base.logger
|
50
|
-
|
51
|
-
|
48
|
+
SolidQueue.instrument(:polling) do
|
49
|
+
if SolidQueue.silence_polling? && ActiveRecord::Base.logger
|
50
|
+
ActiveRecord::Base.logger.silence { yield }
|
51
|
+
else
|
52
|
+
yield
|
53
|
+
end
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
module Processes
|
5
|
+
class ProcessExitError < RuntimeError
|
6
|
+
def initialize(status)
|
7
|
+
message = "Process pid=#{status.pid} exited unexpectedly."
|
8
|
+
if status.exitstatus.present?
|
9
|
+
message += " Exited with status #{status.exitstatus}."
|
10
|
+
end
|
11
|
+
|
12
|
+
if status.signaled?
|
13
|
+
message += " Received unhandled signal #{status.termsig}."
|
14
|
+
end
|
15
|
+
|
16
|
+
super(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
module Processes
|
5
|
+
class ProcessPrunedError < RuntimeError
|
6
|
+
def initialize(last_heartbeat_at)
|
7
|
+
super("Process was found dead and pruned (last heartbeat at: #{last_heartbeat_at}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -1,10 +1,4 @@
|
|
1
1
|
module SolidQueue
|
2
|
-
class ProcessMissingError < RuntimeError
|
3
|
-
def initialize
|
4
|
-
super("The process that was running this job no longer exists")
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
2
|
module Supervisor::Maintenance
|
9
3
|
extend ActiveSupport::Concern
|
10
4
|
|
@@ -35,7 +29,7 @@ module SolidQueue
|
|
35
29
|
|
36
30
|
def fail_orphaned_executions
|
37
31
|
wrap_in_app_executor do
|
38
|
-
|
32
|
+
ClaimedExecution.orphaned.fail_all_with(Processes::ProcessMissingError.new)
|
39
33
|
end
|
40
34
|
end
|
41
35
|
end
|
@@ -2,20 +2,26 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Supervisor < Processes::Base
|
5
|
-
include Maintenance
|
5
|
+
include Maintenance, Signals, Pidfiled
|
6
6
|
|
7
7
|
class << self
|
8
|
-
def start(
|
8
|
+
def start(load_configuration_from: nil)
|
9
9
|
SolidQueue.supervisor = true
|
10
|
-
configuration = Configuration.new(
|
10
|
+
configuration = Configuration.new(load_from: load_configuration_from)
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
if configuration.configured_processes.any?
|
13
|
+
new(configuration).tap(&:start)
|
14
|
+
else
|
15
|
+
abort "No workers or processed configured. Exiting..."
|
16
|
+
end
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
17
20
|
def initialize(configuration)
|
18
21
|
@configuration = configuration
|
22
|
+
@forks = {}
|
23
|
+
@configured_processes = {}
|
24
|
+
|
19
25
|
super
|
20
26
|
end
|
21
27
|
|
@@ -33,7 +39,7 @@ module SolidQueue
|
|
33
39
|
end
|
34
40
|
|
35
41
|
private
|
36
|
-
attr_reader :configuration
|
42
|
+
attr_reader :configuration, :forks, :configured_processes
|
37
43
|
|
38
44
|
def boot
|
39
45
|
SolidQueue.instrument(:start_process, process: self) do
|
@@ -48,15 +54,63 @@ module SolidQueue
|
|
48
54
|
configuration.configured_processes.each { |configured_process| start_process(configured_process) }
|
49
55
|
end
|
50
56
|
|
57
|
+
def supervise
|
58
|
+
loop do
|
59
|
+
break if stopped?
|
60
|
+
|
61
|
+
set_procline
|
62
|
+
process_signal_queue
|
63
|
+
|
64
|
+
unless stopped?
|
65
|
+
reap_and_replace_terminated_forks
|
66
|
+
interruptible_sleep(1.second)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
ensure
|
70
|
+
shutdown
|
71
|
+
end
|
72
|
+
|
73
|
+
def start_process(configured_process)
|
74
|
+
process_instance = configured_process.instantiate.tap do |instance|
|
75
|
+
instance.supervised_by process
|
76
|
+
instance.mode = :fork
|
77
|
+
end
|
78
|
+
|
79
|
+
pid = fork do
|
80
|
+
process_instance.start
|
81
|
+
end
|
82
|
+
|
83
|
+
configured_processes[pid] = configured_process
|
84
|
+
forks[pid] = process_instance
|
85
|
+
end
|
86
|
+
|
51
87
|
def stopped?
|
52
88
|
@stopped
|
53
89
|
end
|
54
90
|
|
55
|
-
def
|
91
|
+
def set_procline
|
92
|
+
procline "supervising #{supervised_processes.join(", ")}"
|
56
93
|
end
|
57
94
|
|
58
|
-
def
|
59
|
-
|
95
|
+
def terminate_gracefully
|
96
|
+
SolidQueue.instrument(:graceful_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: supervised_processes) do |payload|
|
97
|
+
term_forks
|
98
|
+
|
99
|
+
Timer.wait_until(SolidQueue.shutdown_timeout, -> { all_forks_terminated? }) do
|
100
|
+
reap_terminated_forks
|
101
|
+
end
|
102
|
+
|
103
|
+
unless all_forks_terminated?
|
104
|
+
payload[:shutdown_timeout_exceeded] = true
|
105
|
+
terminate_immediately
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def terminate_immediately
|
111
|
+
SolidQueue.instrument(:immediate_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: supervised_processes) do
|
112
|
+
quit_forks
|
113
|
+
end
|
60
114
|
end
|
61
115
|
|
62
116
|
def shutdown
|
@@ -70,5 +124,63 @@ module SolidQueue
|
|
70
124
|
def sync_std_streams
|
71
125
|
STDOUT.sync = STDERR.sync = true
|
72
126
|
end
|
127
|
+
|
128
|
+
def supervised_processes
|
129
|
+
forks.keys
|
130
|
+
end
|
131
|
+
|
132
|
+
def term_forks
|
133
|
+
signal_processes(forks.keys, :TERM)
|
134
|
+
end
|
135
|
+
|
136
|
+
def quit_forks
|
137
|
+
signal_processes(forks.keys, :QUIT)
|
138
|
+
end
|
139
|
+
|
140
|
+
def reap_and_replace_terminated_forks
|
141
|
+
loop do
|
142
|
+
pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
|
143
|
+
break unless pid
|
144
|
+
|
145
|
+
replace_fork(pid, status)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def reap_terminated_forks
|
150
|
+
loop do
|
151
|
+
pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
|
152
|
+
break unless pid
|
153
|
+
|
154
|
+
if (terminated_fork = forks.delete(pid)) && (!status.exited? || status.exitstatus > 0)
|
155
|
+
handle_claimed_jobs_by(terminated_fork, status)
|
156
|
+
end
|
157
|
+
|
158
|
+
configured_processes.delete(pid)
|
159
|
+
end
|
160
|
+
rescue SystemCallError
|
161
|
+
# All children already reaped
|
162
|
+
end
|
163
|
+
|
164
|
+
def replace_fork(pid, status)
|
165
|
+
SolidQueue.instrument(:replace_fork, supervisor_pid: ::Process.pid, pid: pid, status: status) do |payload|
|
166
|
+
if terminated_fork = forks.delete(pid)
|
167
|
+
payload[:fork] = terminated_fork
|
168
|
+
handle_claimed_jobs_by(terminated_fork, status)
|
169
|
+
|
170
|
+
start_process(configured_processes.delete(pid))
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def handle_claimed_jobs_by(terminated_fork, status)
|
176
|
+
if registered_process = process.supervisees.find_by(name: terminated_fork.name)
|
177
|
+
error = Processes::ProcessExitError.new(status)
|
178
|
+
registered_process.fail_all_claimed_executions_with(error)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def all_forks_terminated?
|
183
|
+
forks.empty?
|
184
|
+
end
|
73
185
|
end
|
74
186
|
end
|
data/lib/solid_queue/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rosa Gutierrez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 1.11.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: thor
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.3.1
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.3.1
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: debug
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -188,6 +202,7 @@ files:
|
|
188
202
|
- MIT-LICENSE
|
189
203
|
- README.md
|
190
204
|
- Rakefile
|
205
|
+
- UPGRADING.md
|
191
206
|
- app/jobs/solid_queue/recurring_job.rb
|
192
207
|
- app/models/solid_queue/blocked_execution.rb
|
193
208
|
- app/models/solid_queue/claimed_execution.rb
|
@@ -228,9 +243,11 @@ files:
|
|
228
243
|
- lib/generators/solid_queue/install/USAGE
|
229
244
|
- lib/generators/solid_queue/install/install_generator.rb
|
230
245
|
- lib/generators/solid_queue/install/templates/config.yml
|
246
|
+
- lib/generators/solid_queue/install/templates/jobs
|
231
247
|
- lib/puma/plugin/solid_queue.rb
|
232
248
|
- lib/solid_queue.rb
|
233
249
|
- lib/solid_queue/app_executor.rb
|
250
|
+
- lib/solid_queue/cli.rb
|
234
251
|
- lib/solid_queue/configuration.rb
|
235
252
|
- lib/solid_queue/dispatcher.rb
|
236
253
|
- lib/solid_queue/dispatcher/concurrency_maintenance.rb
|
@@ -242,13 +259,14 @@ files:
|
|
242
259
|
- lib/solid_queue/processes/callbacks.rb
|
243
260
|
- lib/solid_queue/processes/interruptible.rb
|
244
261
|
- lib/solid_queue/processes/poller.rb
|
262
|
+
- lib/solid_queue/processes/process_exit_error.rb
|
263
|
+
- lib/solid_queue/processes/process_missing_error.rb
|
264
|
+
- lib/solid_queue/processes/process_pruned_error.rb
|
245
265
|
- lib/solid_queue/processes/procline.rb
|
246
266
|
- lib/solid_queue/processes/registrable.rb
|
247
267
|
- lib/solid_queue/processes/runnable.rb
|
248
268
|
- lib/solid_queue/processes/supervised.rb
|
249
269
|
- lib/solid_queue/supervisor.rb
|
250
|
-
- lib/solid_queue/supervisor/async_supervisor.rb
|
251
|
-
- lib/solid_queue/supervisor/fork_supervisor.rb
|
252
270
|
- lib/solid_queue/supervisor/maintenance.rb
|
253
271
|
- lib/solid_queue/supervisor/pidfile.rb
|
254
272
|
- lib/solid_queue/supervisor/pidfiled.rb
|
@@ -264,8 +282,9 @@ metadata:
|
|
264
282
|
homepage_uri: https://github.com/rails/solid_queue
|
265
283
|
source_code_uri: https://github.com/rails/solid_queue
|
266
284
|
post_install_message: |
|
267
|
-
Upgrading to Solid Queue 0.4.x? There are some breaking changes about how Solid Queue is started
|
268
|
-
https://github.com/rails/solid_queue/blob/main/UPGRADING.md
|
285
|
+
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,
|
286
|
+
configuration and new migrations. Check https://github.com/rails/solid_queue/blob/main/UPGRADING.md
|
287
|
+
for upgrade instructions.
|
269
288
|
rdoc_options: []
|
270
289
|
require_paths:
|
271
290
|
- lib
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SolidQueue
|
4
|
-
class Supervisor::AsyncSupervisor < Supervisor
|
5
|
-
def initialize(*)
|
6
|
-
super
|
7
|
-
@threads = Concurrent::Map.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def kind
|
11
|
-
"Supervisor(async)"
|
12
|
-
end
|
13
|
-
|
14
|
-
def stop
|
15
|
-
super
|
16
|
-
stop_threads
|
17
|
-
threads.clear
|
18
|
-
|
19
|
-
shutdown
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
attr_reader :threads
|
24
|
-
|
25
|
-
def start_process(configured_process)
|
26
|
-
process_instance = configured_process.instantiate.tap do |instance|
|
27
|
-
instance.supervised_by process
|
28
|
-
end
|
29
|
-
|
30
|
-
process_instance.start
|
31
|
-
|
32
|
-
threads[process_instance.name] = process_instance
|
33
|
-
end
|
34
|
-
|
35
|
-
def stop_threads
|
36
|
-
stop_threads = threads.values.map do |thr|
|
37
|
-
Thread.new { thr.stop }
|
38
|
-
end
|
39
|
-
|
40
|
-
stop_threads.each { |thr| thr.join(SolidQueue.shutdown_timeout) }
|
41
|
-
end
|
42
|
-
|
43
|
-
def all_threads_terminated?
|
44
|
-
threads.values.none?(&:alive?)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,138 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SolidQueue
|
4
|
-
class ProcessExitError < RuntimeError
|
5
|
-
def initialize(status)
|
6
|
-
message = case
|
7
|
-
when status.exitstatus.present? then "Process pid=#{status.pid} exited with status #{status.exitstatus}"
|
8
|
-
when status.signaled? then "Process pid=#{status.pid} received unhandled signal #{status.termsig}"
|
9
|
-
else "Process pid=#{status.pid} exited unexpectedly"
|
10
|
-
end
|
11
|
-
|
12
|
-
super(message)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class Supervisor::ForkSupervisor < Supervisor
|
17
|
-
include Signals, Pidfiled
|
18
|
-
|
19
|
-
def initialize(*)
|
20
|
-
super
|
21
|
-
|
22
|
-
@forks = {}
|
23
|
-
@configured_processes = {}
|
24
|
-
end
|
25
|
-
|
26
|
-
def kind
|
27
|
-
"Supervisor(fork)"
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
attr_reader :forks, :configured_processes
|
32
|
-
|
33
|
-
def supervise
|
34
|
-
loop do
|
35
|
-
break if stopped?
|
36
|
-
|
37
|
-
procline "supervising #{forks.keys.join(", ")}"
|
38
|
-
process_signal_queue
|
39
|
-
|
40
|
-
unless stopped?
|
41
|
-
reap_and_replace_terminated_forks
|
42
|
-
interruptible_sleep(1.second)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
ensure
|
46
|
-
shutdown
|
47
|
-
end
|
48
|
-
|
49
|
-
def start_process(configured_process)
|
50
|
-
process_instance = configured_process.instantiate.tap do |instance|
|
51
|
-
instance.supervised_by process
|
52
|
-
instance.mode = :fork
|
53
|
-
end
|
54
|
-
|
55
|
-
pid = fork do
|
56
|
-
process_instance.start
|
57
|
-
end
|
58
|
-
|
59
|
-
configured_processes[pid] = configured_process
|
60
|
-
forks[pid] = process_instance
|
61
|
-
end
|
62
|
-
|
63
|
-
def terminate_gracefully
|
64
|
-
SolidQueue.instrument(:graceful_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: forks.keys) do |payload|
|
65
|
-
term_forks
|
66
|
-
|
67
|
-
Timer.wait_until(SolidQueue.shutdown_timeout, -> { all_forks_terminated? }) do
|
68
|
-
reap_terminated_forks
|
69
|
-
end
|
70
|
-
|
71
|
-
unless all_forks_terminated?
|
72
|
-
payload[:shutdown_timeout_exceeded] = true
|
73
|
-
terminate_immediately
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def terminate_immediately
|
79
|
-
SolidQueue.instrument(:immediate_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: forks.keys) do
|
80
|
-
quit_forks
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def term_forks
|
85
|
-
signal_processes(forks.keys, :TERM)
|
86
|
-
end
|
87
|
-
|
88
|
-
def quit_forks
|
89
|
-
signal_processes(forks.keys, :QUIT)
|
90
|
-
end
|
91
|
-
|
92
|
-
def reap_and_replace_terminated_forks
|
93
|
-
loop do
|
94
|
-
pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
|
95
|
-
break unless pid
|
96
|
-
|
97
|
-
replace_fork(pid, status)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def reap_terminated_forks
|
102
|
-
loop do
|
103
|
-
pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
|
104
|
-
break unless pid
|
105
|
-
|
106
|
-
if (terminated_fork = forks.delete(pid)) && !status.exited? || status.exitstatus > 0
|
107
|
-
handle_claimed_jobs_by(terminated_fork, status)
|
108
|
-
end
|
109
|
-
|
110
|
-
configured_processes.delete(pid)
|
111
|
-
end
|
112
|
-
rescue SystemCallError
|
113
|
-
# All children already reaped
|
114
|
-
end
|
115
|
-
|
116
|
-
def replace_fork(pid, status)
|
117
|
-
SolidQueue.instrument(:replace_fork, supervisor_pid: ::Process.pid, pid: pid, status: status) do |payload|
|
118
|
-
if terminated_fork = forks.delete(pid)
|
119
|
-
payload[:fork] = terminated_fork
|
120
|
-
handle_claimed_jobs_by(terminated_fork, status)
|
121
|
-
|
122
|
-
start_process(configured_processes.delete(pid))
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def handle_claimed_jobs_by(terminated_fork, status)
|
128
|
-
if registered_process = process.supervisees.find_by(name: terminated_fork.name)
|
129
|
-
error = ProcessExitError.new(status)
|
130
|
-
registered_process.fail_all_claimed_executions_with(error)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def all_forks_terminated?
|
135
|
-
forks.empty?
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|