solid_queue 0.5.0 → 0.6.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/app/models/solid_queue/claimed_execution.rb +21 -8
- data/app/models/solid_queue/process/executor.rb +13 -1
- data/app/models/solid_queue/process/prunable.rb +8 -1
- data/app/models/solid_queue/process.rb +13 -6
- data/app/models/solid_queue/recurring_execution.rb +1 -1
- data/app/models/solid_queue/recurring_task.rb +10 -3
- data/db/migrate/20240811173327_add_name_to_processes.rb +5 -0
- data/db/migrate/20240813160053_make_name_not_null.rb +16 -0
- data/db/migrate/20240819165045_change_solid_queue_recurring_tasks_static_to_not_null.rb +5 -0
- data/lib/solid_queue/configuration.rb +41 -25
- data/lib/solid_queue/log_subscriber.rb +13 -6
- data/lib/solid_queue/processes/base.rb +11 -0
- data/lib/solid_queue/processes/poller.rb +8 -4
- data/lib/solid_queue/processes/process_exit_error.rb +17 -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/processes/registrable.rb +1 -0
- data/lib/solid_queue/processes/runnable.rb +0 -4
- data/lib/solid_queue/supervisor/async_supervisor.rb +6 -3
- data/lib/solid_queue/supervisor/fork_supervisor.rb +27 -9
- data/lib/solid_queue/supervisor/maintenance.rb +5 -3
- data/lib/solid_queue/supervisor.rb +8 -3
- data/lib/solid_queue/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2734f6cbcc795345207dc0cf221bd2744f2f98449da8b0edfc8175f3bbd7869
|
4
|
+
data.tar.gz: c4b4c2eca7dcb93e86a2f69d220ba1b9f8817c1d52a7cd1ff137ed5ec84e50f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d09b58e43c4bc19ad2ab89d10072b36b53a449e9602f5c5a8fc7b637e91d8472eb70f417148657e6b146e5cd1034025afdbe6d39fa2b9fc82ce6bba3997e57f
|
7
|
+
data.tar.gz: 27168fd2216fbca4e9b19677a7885b23663012d8aed7bf54c25be8335bd528a733b55790e662a30815b9ba24d824390372eff24ed0b9f72947b6ff4feeec17c5
|
@@ -29,8 +29,21 @@ 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
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def fail_all_with(error)
|
40
|
+
SolidQueue.instrument(:fail_many_claimed) do |payload|
|
41
|
+
includes(:job).tap do |executions|
|
42
|
+
executions.each { |execution| execution.failed_with(error) }
|
43
|
+
|
44
|
+
payload[:process_ids] = executions.map(&:process_id).uniq
|
45
|
+
payload[:job_ids] = executions.map(&:job_id).uniq
|
46
|
+
payload[:size] = executions.size
|
34
47
|
end
|
35
48
|
end
|
36
49
|
end
|
@@ -69,6 +82,13 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
69
82
|
raise UndiscardableError, "Can't discard a job in progress"
|
70
83
|
end
|
71
84
|
|
85
|
+
def failed_with(error)
|
86
|
+
transaction do
|
87
|
+
job.failed_with(error)
|
88
|
+
destroy!
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
72
92
|
private
|
73
93
|
def execute
|
74
94
|
ActiveJob::Base.execute(job.arguments)
|
@@ -83,11 +103,4 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
|
|
83
103
|
destroy!
|
84
104
|
end
|
85
105
|
end
|
86
|
-
|
87
|
-
def failed_with(error)
|
88
|
-
transaction do
|
89
|
-
job.failed_with(error)
|
90
|
-
destroy!
|
91
|
-
end
|
92
|
-
end
|
93
106
|
end
|
@@ -8,7 +8,19 @@ 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
|
+
end
|
13
|
+
|
14
|
+
def fail_all_claimed_executions_with(error)
|
15
|
+
if claims_executions?
|
16
|
+
claimed_executions.fail_all_with(error)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def release_all_claimed_executions
|
21
|
+
if claims_executions?
|
22
|
+
claimed_executions.release_all
|
23
|
+
end
|
12
24
|
end
|
13
25
|
|
14
26
|
private
|
@@ -15,11 +15,18 @@ module SolidQueue
|
|
15
15
|
prunable.non_blocking_lock.find_in_batches(batch_size: 50) do |batch|
|
16
16
|
payload[:size] += batch.size
|
17
17
|
|
18
|
-
batch.each
|
18
|
+
batch.each(&:prune)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
def prune
|
25
|
+
error = Processes::ProcessPrunedError.new(last_heartbeat_at)
|
26
|
+
fail_all_claimed_executions_with(error)
|
27
|
+
|
28
|
+
deregister(pruned: true)
|
29
|
+
end
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
@@ -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
|
|
@@ -13,10 +13,10 @@ class SolidQueue::Process < SolidQueue::Record
|
|
13
13
|
create!(attributes.merge(last_heartbeat_at: Time.current)).tap do |process|
|
14
14
|
payload[:process_id] = process.id
|
15
15
|
end
|
16
|
+
rescue Exception => error
|
17
|
+
payload[:error] = error
|
18
|
+
raise
|
16
19
|
end
|
17
|
-
rescue Exception => error
|
18
|
-
SolidQueue.instrument :register_process, **attributes.merge(error: error)
|
19
|
-
raise
|
20
20
|
end
|
21
21
|
|
22
22
|
def heartbeat
|
@@ -25,12 +25,19 @@ class SolidQueue::Process < SolidQueue::Record
|
|
25
25
|
|
26
26
|
def deregister(pruned: false)
|
27
27
|
SolidQueue.instrument :deregister_process, process: self, pruned: pruned do |payload|
|
28
|
-
payload[:claimed_size] = claimed_executions.size if claims_executions?
|
29
|
-
|
30
28
|
destroy!
|
29
|
+
|
30
|
+
unless supervised? || pruned
|
31
|
+
supervisees.each(&:deregister)
|
32
|
+
end
|
31
33
|
rescue Exception => error
|
32
34
|
payload[:error] = error
|
33
35
|
raise
|
34
36
|
end
|
35
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def supervised?
|
41
|
+
supervisor_id.present?
|
42
|
+
end
|
36
43
|
end
|
@@ -25,7 +25,7 @@ module SolidQueue
|
|
25
25
|
def record(task_key, run_at, &block)
|
26
26
|
transaction do
|
27
27
|
block.call.tap do |active_job|
|
28
|
-
if active_job
|
28
|
+
if active_job && active_job.successfully_enqueued?
|
29
29
|
create_or_insert!(job_id: active_job.provider_job_id, task_key: task_key, run_at: run_at)
|
30
30
|
end
|
31
31
|
end
|
@@ -43,7 +43,7 @@ module SolidQueue
|
|
43
43
|
def enqueue(at:)
|
44
44
|
SolidQueue.instrument(:enqueue_recurring_task, task: key, at: at) do |payload|
|
45
45
|
active_job = if using_solid_queue_adapter?
|
46
|
-
|
46
|
+
enqueue_and_record(run_at: at)
|
47
47
|
else
|
48
48
|
payload[:other_adapter] = true
|
49
49
|
|
@@ -87,8 +87,15 @@ module SolidQueue
|
|
87
87
|
job_class.queue_adapter_name.inquiry.solid_queue?
|
88
88
|
end
|
89
89
|
|
90
|
-
def
|
91
|
-
RecurringExecution.record(key, run_at)
|
90
|
+
def enqueue_and_record(run_at:)
|
91
|
+
RecurringExecution.record(key, run_at) do
|
92
|
+
job_class.new(*arguments_with_kwargs).tap do |active_job|
|
93
|
+
active_job.run_callbacks(:enqueue) do
|
94
|
+
Job.enqueue(active_job)
|
95
|
+
end
|
96
|
+
active_job.successfully_enqueued = true
|
97
|
+
end
|
98
|
+
end
|
92
99
|
end
|
93
100
|
|
94
101
|
def perform_later(&block)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class MakeNameNotNull < ActiveRecord::Migration[7.1]
|
2
|
+
def up
|
3
|
+
SolidQueue::Process.where(name: nil).find_each do |process|
|
4
|
+
process.name ||= [ process.kind.downcase, SecureRandom.hex(10) ].join("-")
|
5
|
+
process.save!
|
6
|
+
end
|
7
|
+
|
8
|
+
change_column :solid_queue_processes, :name, :string, null: false
|
9
|
+
add_index :solid_queue_processes, [ :name, :supervisor_id ], unique: true
|
10
|
+
end
|
11
|
+
|
12
|
+
def down
|
13
|
+
remove_index :solid_queue_processes, [ :name, :supervisor_id ]
|
14
|
+
change_column :solid_queue_processes, :name, :string, null: true
|
15
|
+
end
|
16
|
+
end
|
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
module SolidQueue
|
4
4
|
class Configuration
|
5
|
+
class Process < Struct.new(:kind, :attributes)
|
6
|
+
def instantiate
|
7
|
+
"SolidQueue::#{kind.to_s.titleize}".safe_constantize.new(**attributes)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
5
11
|
WORKER_DEFAULTS = {
|
6
12
|
queues: "*",
|
7
13
|
threads: 3,
|
@@ -17,33 +23,20 @@ module SolidQueue
|
|
17
23
|
recurring_tasks: []
|
18
24
|
}
|
19
25
|
|
26
|
+
DEFAULT_CONFIG = {
|
27
|
+
workers: [ WORKER_DEFAULTS ],
|
28
|
+
dispatchers: [ DISPATCHER_DEFAULTS ]
|
29
|
+
}
|
30
|
+
|
20
31
|
def initialize(mode: :fork, load_from: nil)
|
21
32
|
@mode = mode.to_s.inquiry
|
22
33
|
@raw_config = config_from(load_from)
|
23
34
|
end
|
24
35
|
|
25
|
-
def
|
36
|
+
def configured_processes
|
26
37
|
dispatchers + workers
|
27
38
|
end
|
28
39
|
|
29
|
-
def workers
|
30
|
-
workers_options.flat_map do |worker_options|
|
31
|
-
processes = if mode.fork?
|
32
|
-
worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
|
33
|
-
else
|
34
|
-
WORKER_DEFAULTS[:processes]
|
35
|
-
end
|
36
|
-
processes.times.map { Worker.new(**worker_options.with_defaults(WORKER_DEFAULTS)) }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def dispatchers
|
41
|
-
dispatchers_options.map do |dispatcher_options|
|
42
|
-
recurring_tasks = parse_recurring_tasks dispatcher_options[:recurring_tasks]
|
43
|
-
Dispatcher.new **dispatcher_options.merge(recurring_tasks: recurring_tasks).with_defaults(DISPATCHER_DEFAULTS)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
40
|
def max_number_of_threads
|
48
41
|
# At most "threads" in each worker + 1 thread for the worker + 1 thread for the heartbeat task
|
49
42
|
workers_options.map { |options| options[:threads] }.max + 2
|
@@ -54,23 +47,46 @@ module SolidQueue
|
|
54
47
|
|
55
48
|
DEFAULT_CONFIG_FILE_PATH = "config/solid_queue.yml"
|
56
49
|
|
50
|
+
def workers
|
51
|
+
workers_options.flat_map do |worker_options|
|
52
|
+
processes = if mode.fork?
|
53
|
+
worker_options.fetch(:processes, WORKER_DEFAULTS[:processes])
|
54
|
+
else
|
55
|
+
WORKER_DEFAULTS[:processes]
|
56
|
+
end
|
57
|
+
processes.times.map { Process.new(:worker, worker_options.with_defaults(WORKER_DEFAULTS)) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def dispatchers
|
62
|
+
dispatchers_options.map do |dispatcher_options|
|
63
|
+
recurring_tasks = parse_recurring_tasks dispatcher_options[:recurring_tasks]
|
64
|
+
Process.new :dispatcher, dispatcher_options.merge(recurring_tasks: recurring_tasks).with_defaults(DISPATCHER_DEFAULTS)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
57
68
|
def config_from(file_or_hash, env: Rails.env)
|
58
|
-
|
59
|
-
|
69
|
+
load_config_from(file_or_hash).then do |config|
|
70
|
+
config = config[env.to_sym] ? config[env.to_sym] : config
|
71
|
+
if (config.keys & DEFAULT_CONFIG.keys).any? then config
|
72
|
+
else
|
73
|
+
DEFAULT_CONFIG
|
74
|
+
end
|
75
|
+
end
|
60
76
|
end
|
61
77
|
|
62
78
|
def workers_options
|
63
|
-
@workers_options ||= options_from_raw_config(:workers
|
79
|
+
@workers_options ||= options_from_raw_config(:workers)
|
64
80
|
.map { |options| options.dup.symbolize_keys }
|
65
81
|
end
|
66
82
|
|
67
83
|
def dispatchers_options
|
68
|
-
@dispatchers_options ||= options_from_raw_config(:dispatchers
|
84
|
+
@dispatchers_options ||= options_from_raw_config(:dispatchers)
|
69
85
|
.map { |options| options.dup.symbolize_keys }
|
70
86
|
end
|
71
87
|
|
72
|
-
def options_from_raw_config(key
|
73
|
-
|
88
|
+
def options_from_raw_config(key)
|
89
|
+
Array(raw_config[key])
|
74
90
|
end
|
75
91
|
|
76
92
|
def parse_recurring_tasks(tasks)
|
@@ -12,11 +12,15 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def release_many_claimed(event)
|
15
|
-
|
15
|
+
info formatted_event(event, action: "Release claimed jobs", **event.payload.slice(:size))
|
16
|
+
end
|
17
|
+
|
18
|
+
def fail_many_claimed(event)
|
19
|
+
warn formatted_event(event, action: "Fail claimed jobs", **event.payload.slice(:job_ids, :process_ids))
|
16
20
|
end
|
17
21
|
|
18
22
|
def release_claimed(event)
|
19
|
-
|
23
|
+
info formatted_event(event, action: "Release claimed job", **event.payload.slice(:job_id, :process_id))
|
20
24
|
end
|
21
25
|
|
22
26
|
def retry_all(event)
|
@@ -63,7 +67,8 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
63
67
|
attributes = {
|
64
68
|
pid: process.pid,
|
65
69
|
hostname: process.hostname,
|
66
|
-
process_id: process.process_id
|
70
|
+
process_id: process.process_id,
|
71
|
+
name: process.name
|
67
72
|
}.merge(process.metadata)
|
68
73
|
|
69
74
|
info formatted_event(event, action: "Started #{process.kind}", **attributes)
|
@@ -75,7 +80,8 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
75
80
|
attributes = {
|
76
81
|
pid: process.pid,
|
77
82
|
hostname: process.hostname,
|
78
|
-
process_id: process.process_id
|
83
|
+
process_id: process.process_id,
|
84
|
+
name: process.name
|
79
85
|
}.merge(process.metadata)
|
80
86
|
|
81
87
|
info formatted_event(event, action: "Shutdown #{process.kind}", **attributes)
|
@@ -83,7 +89,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
83
89
|
|
84
90
|
def register_process(event)
|
85
91
|
process_kind = event.payload[:kind]
|
86
|
-
attributes = event.payload.slice(:pid, :hostname, :process_id)
|
92
|
+
attributes = event.payload.slice(:pid, :hostname, :process_id, :name)
|
87
93
|
|
88
94
|
if error = event.payload[:error]
|
89
95
|
warn formatted_event(event, action: "Error registering #{process_kind}", **attributes.merge(error: formatted_error(error)))
|
@@ -99,6 +105,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
99
105
|
process_id: process.id,
|
100
106
|
pid: process.pid,
|
101
107
|
hostname: process.hostname,
|
108
|
+
name: process.name,
|
102
109
|
last_heartbeat_at: process.last_heartbeat_at.iso8601,
|
103
110
|
claimed_size: event.payload[:claimed_size],
|
104
111
|
pruned: event.payload[:pruned]
|
@@ -147,7 +154,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
147
154
|
termsig: status.termsig
|
148
155
|
|
149
156
|
if replaced_fork = event.payload[:fork]
|
150
|
-
info formatted_event(event, action: "Replaced terminated #{replaced_fork.kind}", **attributes.merge(hostname: replaced_fork.hostname))
|
157
|
+
info formatted_event(event, action: "Replaced terminated #{replaced_fork.kind}", **attributes.merge(hostname: replaced_fork.hostname, name: replaced_fork.name))
|
151
158
|
else
|
152
159
|
warn formatted_event(event, action: "Tried to replace forked process but it had already died", **attributes)
|
153
160
|
end
|
@@ -6,6 +6,12 @@ module SolidQueue
|
|
6
6
|
include Callbacks # Defines callbacks needed by other concerns
|
7
7
|
include AppExecutor, Registrable, Interruptible, Procline
|
8
8
|
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(*)
|
12
|
+
@name = generate_name
|
13
|
+
end
|
14
|
+
|
9
15
|
def kind
|
10
16
|
self.class.name.demodulize
|
11
17
|
end
|
@@ -21,6 +27,11 @@ module SolidQueue
|
|
21
27
|
def metadata
|
22
28
|
{}
|
23
29
|
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def generate_name
|
33
|
+
[ kind.downcase, SecureRandom.hex(10) ].join("-")
|
34
|
+
end
|
24
35
|
end
|
25
36
|
end
|
26
37
|
end
|
@@ -8,6 +8,8 @@ module SolidQueue::Processes
|
|
8
8
|
|
9
9
|
def initialize(polling_interval:, **options)
|
10
10
|
@polling_interval = polling_interval
|
11
|
+
|
12
|
+
super(**options)
|
11
13
|
end
|
12
14
|
|
13
15
|
def metadata
|
@@ -43,10 +45,12 @@ module SolidQueue::Processes
|
|
43
45
|
end
|
44
46
|
|
45
47
|
def with_polling_volume
|
46
|
-
|
47
|
-
ActiveRecord::Base.logger
|
48
|
-
|
49
|
-
|
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
|
50
54
|
end
|
51
55
|
end
|
52
56
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
module Processes
|
5
|
+
class ProcessExitError < RuntimeError
|
6
|
+
def initialize(status)
|
7
|
+
message = case
|
8
|
+
when status.exitstatus.present? then "Process pid=#{status.pid} exited with status #{status. exitstatus}"
|
9
|
+
when status.signaled? then "Process pid=#{status.pid} received unhandled signal #{status. termsig}"
|
10
|
+
else "Process pid=#{status.pid} exited unexpectedly"
|
11
|
+
end
|
12
|
+
|
13
|
+
super(message)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
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
|
@@ -23,10 +23,13 @@ module SolidQueue
|
|
23
23
|
attr_reader :threads
|
24
24
|
|
25
25
|
def start_process(configured_process)
|
26
|
-
configured_process.
|
27
|
-
|
26
|
+
process_instance = configured_process.instantiate.tap do |instance|
|
27
|
+
instance.supervised_by process
|
28
|
+
end
|
29
|
+
|
30
|
+
process_instance.start
|
28
31
|
|
29
|
-
threads[
|
32
|
+
threads[process_instance.name] = process_instance
|
30
33
|
end
|
31
34
|
|
32
35
|
def stop_threads
|
@@ -6,7 +6,9 @@ module SolidQueue
|
|
6
6
|
|
7
7
|
def initialize(*)
|
8
8
|
super
|
9
|
+
|
9
10
|
@forks = {}
|
11
|
+
@configured_processes = {}
|
10
12
|
end
|
11
13
|
|
12
14
|
def kind
|
@@ -14,7 +16,7 @@ module SolidQueue
|
|
14
16
|
end
|
15
17
|
|
16
18
|
private
|
17
|
-
attr_reader :forks
|
19
|
+
attr_reader :forks, :configured_processes
|
18
20
|
|
19
21
|
def supervise
|
20
22
|
loop do
|
@@ -33,14 +35,17 @@ module SolidQueue
|
|
33
35
|
end
|
34
36
|
|
35
37
|
def start_process(configured_process)
|
36
|
-
configured_process.
|
37
|
-
|
38
|
+
process_instance = configured_process.instantiate.tap do |instance|
|
39
|
+
instance.supervised_by process
|
40
|
+
instance.mode = :fork
|
41
|
+
end
|
38
42
|
|
39
43
|
pid = fork do
|
40
|
-
|
44
|
+
process_instance.start
|
41
45
|
end
|
42
46
|
|
43
|
-
|
47
|
+
configured_processes[pid] = configured_process
|
48
|
+
forks[pid] = process_instance
|
44
49
|
end
|
45
50
|
|
46
51
|
def terminate_gracefully
|
@@ -86,7 +91,11 @@ module SolidQueue
|
|
86
91
|
pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
|
87
92
|
break unless pid
|
88
93
|
|
89
|
-
forks.delete(pid)
|
94
|
+
if (terminated_fork = forks.delete(pid)) && !status.exited? || status.exitstatus > 0
|
95
|
+
handle_claimed_jobs_by(terminated_fork, status)
|
96
|
+
end
|
97
|
+
|
98
|
+
configured_processes.delete(pid)
|
90
99
|
end
|
91
100
|
rescue SystemCallError
|
92
101
|
# All children already reaped
|
@@ -94,13 +103,22 @@ module SolidQueue
|
|
94
103
|
|
95
104
|
def replace_fork(pid, status)
|
96
105
|
SolidQueue.instrument(:replace_fork, supervisor_pid: ::Process.pid, pid: pid, status: status) do |payload|
|
97
|
-
if
|
98
|
-
payload[:fork] =
|
99
|
-
|
106
|
+
if terminated_fork = forks.delete(pid)
|
107
|
+
payload[:fork] = terminated_fork
|
108
|
+
handle_claimed_jobs_by(terminated_fork, status)
|
109
|
+
|
110
|
+
start_process(configured_processes.delete(pid))
|
100
111
|
end
|
101
112
|
end
|
102
113
|
end
|
103
114
|
|
115
|
+
def handle_claimed_jobs_by(terminated_fork, status)
|
116
|
+
if registered_process = process.supervisees.find_by(name: terminated_fork.name)
|
117
|
+
error = Processes::ProcessExitError.new(status)
|
118
|
+
registered_process.fail_all_claimed_executions_with(error)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
104
122
|
def all_forks_terminated?
|
105
123
|
forks.empty?
|
106
124
|
end
|
@@ -3,7 +3,7 @@ module SolidQueue
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
after_boot :
|
6
|
+
after_boot :fail_orphaned_executions
|
7
7
|
end
|
8
8
|
|
9
9
|
private
|
@@ -27,8 +27,10 @@ module SolidQueue
|
|
27
27
|
wrap_in_app_executor { SolidQueue::Process.prune }
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
wrap_in_app_executor
|
30
|
+
def fail_orphaned_executions
|
31
|
+
wrap_in_app_executor do
|
32
|
+
ClaimedExecution.orphaned.fail_all_with(Processes::ProcessMissingError.new)
|
33
|
+
end
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|
@@ -9,13 +9,18 @@ module SolidQueue
|
|
9
9
|
SolidQueue.supervisor = true
|
10
10
|
configuration = Configuration.new(mode: mode, load_from: load_configuration_from)
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
if configuration.configured_processes.any?
|
13
|
+
klass = mode == :fork ? ForkSupervisor : AsyncSupervisor
|
14
|
+
klass.new(configuration).tap(&:start)
|
15
|
+
else
|
16
|
+
abort "No workers or processed configured. Exiting..."
|
17
|
+
end
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
17
21
|
def initialize(configuration)
|
18
22
|
@configuration = configuration
|
23
|
+
super
|
19
24
|
end
|
20
25
|
|
21
26
|
def start
|
@@ -44,7 +49,7 @@ module SolidQueue
|
|
44
49
|
end
|
45
50
|
|
46
51
|
def start_processes
|
47
|
-
configuration.
|
52
|
+
configuration.configured_processes.each { |configured_process| start_process(configured_process) }
|
48
53
|
end
|
49
54
|
|
50
55
|
def stopped?
|
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.6.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-08-
|
11
|
+
date: 2024-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -220,6 +220,9 @@ files:
|
|
220
220
|
- db/migrate/20240110143450_add_missing_index_to_blocked_executions.rb
|
221
221
|
- db/migrate/20240218110712_create_recurring_executions.rb
|
222
222
|
- db/migrate/20240719134516_create_recurring_tasks.rb
|
223
|
+
- db/migrate/20240811173327_add_name_to_processes.rb
|
224
|
+
- db/migrate/20240813160053_make_name_not_null.rb
|
225
|
+
- db/migrate/20240819165045_change_solid_queue_recurring_tasks_static_to_not_null.rb
|
223
226
|
- lib/active_job/concurrency_controls.rb
|
224
227
|
- lib/active_job/queue_adapters/solid_queue_adapter.rb
|
225
228
|
- lib/generators/solid_queue/install/USAGE
|
@@ -239,6 +242,9 @@ files:
|
|
239
242
|
- lib/solid_queue/processes/callbacks.rb
|
240
243
|
- lib/solid_queue/processes/interruptible.rb
|
241
244
|
- lib/solid_queue/processes/poller.rb
|
245
|
+
- lib/solid_queue/processes/process_exit_error.rb
|
246
|
+
- lib/solid_queue/processes/process_missing_error.rb
|
247
|
+
- lib/solid_queue/processes/process_pruned_error.rb
|
242
248
|
- lib/solid_queue/processes/procline.rb
|
243
249
|
- lib/solid_queue/processes/registrable.rb
|
244
250
|
- lib/solid_queue/processes/runnable.rb
|