solid_queue 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +112 -32
- data/app/models/solid_queue/blocked_execution.rb +3 -1
- data/app/models/solid_queue/claimed_execution.rb +4 -2
- data/app/models/solid_queue/failed_execution.rb +6 -3
- data/app/models/solid_queue/job/concurrency_controls.rb +1 -1
- data/app/models/solid_queue/job/retryable.rb +10 -1
- data/app/models/solid_queue/job.rb +1 -0
- data/app/models/solid_queue/ready_execution.rb +2 -1
- data/app/models/solid_queue/record.rb +20 -5
- data/app/models/solid_queue/recurring_execution.rb +1 -1
- data/app/models/solid_queue/recurring_task.rb +27 -9
- data/app/models/solid_queue/semaphore.rb +2 -2
- data/lib/puma/plugin/solid_queue.rb +74 -14
- data/lib/solid_queue/app_executor.rb +10 -0
- data/lib/solid_queue/async_supervisor.rb +52 -0
- data/lib/solid_queue/cli.rb +5 -1
- data/lib/solid_queue/configuration.rb +42 -8
- data/lib/solid_queue/dispatcher.rb +1 -0
- data/lib/solid_queue/fork_supervisor.rb +68 -0
- data/lib/solid_queue/processes/registrable.rb +15 -9
- data/lib/solid_queue/processes/runnable.rb +25 -18
- data/lib/solid_queue/processes/thread_terminated_error.rb +11 -0
- data/lib/solid_queue/scheduler/recurring_schedule.rb +61 -11
- data/lib/solid_queue/scheduler.rb +23 -4
- data/lib/solid_queue/supervisor/maintenance.rb +11 -0
- data/lib/solid_queue/supervisor/signals.rb +2 -2
- data/lib/solid_queue/supervisor.rb +40 -82
- data/lib/solid_queue/timer.rb +3 -3
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue.rb +8 -0
- metadata +25 -9
- data/Rakefile +0 -43
|
@@ -32,5 +32,16 @@ module SolidQueue
|
|
|
32
32
|
ClaimedExecution.orphaned.fail_all_with(Processes::ProcessMissingError.new)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
# When a supervised process crashes or exits we need to mark all the
|
|
37
|
+
# executions it had claimed as failed so that they can be retried
|
|
38
|
+
# by some other worker.
|
|
39
|
+
def release_claimed_jobs_by(terminated_process, with_error:)
|
|
40
|
+
wrap_in_app_executor do
|
|
41
|
+
if registered_process = SolidQueue::Process.find_by(name: terminated_process.name)
|
|
42
|
+
registered_process.fail_all_claimed_executions_with(with_error)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
35
46
|
end
|
|
36
47
|
end
|
|
@@ -6,8 +6,8 @@ module SolidQueue
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
|
-
before_boot :register_signal_handlers
|
|
10
|
-
after_shutdown :restore_default_signal_handlers
|
|
9
|
+
before_boot :register_signal_handlers, if: :standalone?
|
|
10
|
+
after_shutdown :restore_default_signal_handlers, if: :standalone?
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
private
|
|
@@ -13,17 +13,21 @@ module SolidQueue
|
|
|
13
13
|
configuration = Configuration.new(**options)
|
|
14
14
|
|
|
15
15
|
if configuration.valid?
|
|
16
|
-
|
|
16
|
+
klass = configuration.mode.fork? ? ForkSupervisor : AsyncSupervisor
|
|
17
|
+
klass.new(configuration).tap(&:start)
|
|
17
18
|
else
|
|
18
19
|
abort configuration.errors.full_messages.join("\n") + "\nExiting..."
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
|
|
24
|
+
delegate :mode, :standalone?, to: :configuration
|
|
25
|
+
|
|
23
26
|
def initialize(configuration)
|
|
24
27
|
@configuration = configuration
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
@configured_processes = {}
|
|
30
|
+
@process_instances = {}
|
|
27
31
|
|
|
28
32
|
super
|
|
29
33
|
end
|
|
@@ -43,8 +47,12 @@ module SolidQueue
|
|
|
43
47
|
run_stop_hooks
|
|
44
48
|
end
|
|
45
49
|
|
|
50
|
+
def kind
|
|
51
|
+
"Supervisor(#{mode})"
|
|
52
|
+
end
|
|
53
|
+
|
|
46
54
|
private
|
|
47
|
-
attr_reader :configuration, :
|
|
55
|
+
attr_reader :configuration, :configured_processes, :process_instances
|
|
48
56
|
|
|
49
57
|
def boot
|
|
50
58
|
SolidQueue.instrument(:start_process, process: self) do
|
|
@@ -62,11 +70,13 @@ module SolidQueue
|
|
|
62
70
|
loop do
|
|
63
71
|
break if stopped?
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
if standalone?
|
|
74
|
+
set_procline
|
|
75
|
+
process_signal_queue
|
|
76
|
+
end
|
|
67
77
|
|
|
68
78
|
unless stopped?
|
|
69
|
-
|
|
79
|
+
check_and_replace_terminated_processes
|
|
70
80
|
interruptible_sleep(1.second)
|
|
71
81
|
end
|
|
72
82
|
end
|
|
@@ -77,30 +87,23 @@ module SolidQueue
|
|
|
77
87
|
def start_process(configured_process)
|
|
78
88
|
process_instance = configured_process.instantiate.tap do |instance|
|
|
79
89
|
instance.supervised_by process
|
|
80
|
-
instance.mode =
|
|
90
|
+
instance.mode = mode
|
|
81
91
|
end
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
process_instance.start
|
|
85
|
-
end
|
|
93
|
+
process_id = process_instance.start
|
|
86
94
|
|
|
87
|
-
configured_processes[
|
|
88
|
-
|
|
95
|
+
configured_processes[process_id] = configured_process
|
|
96
|
+
process_instances[process_id] = process_instance
|
|
89
97
|
end
|
|
90
98
|
|
|
91
|
-
def
|
|
92
|
-
procline "supervising #{supervised_processes.join(", ")}"
|
|
99
|
+
def check_and_replace_terminated_processes
|
|
93
100
|
end
|
|
94
101
|
|
|
95
102
|
def terminate_gracefully
|
|
96
|
-
SolidQueue.instrument(:graceful_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Timer.wait_until(SolidQueue.shutdown_timeout, -> { all_forks_terminated? }) do
|
|
100
|
-
reap_terminated_forks
|
|
101
|
-
end
|
|
103
|
+
SolidQueue.instrument(:graceful_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: configured_processes.keys) do |payload|
|
|
104
|
+
perform_graceful_termination
|
|
102
105
|
|
|
103
|
-
unless
|
|
106
|
+
unless all_processes_terminated?
|
|
104
107
|
payload[:shutdown_timeout_exceeded] = true
|
|
105
108
|
terminate_immediately
|
|
106
109
|
end
|
|
@@ -108,82 +111,37 @@ module SolidQueue
|
|
|
108
111
|
end
|
|
109
112
|
|
|
110
113
|
def terminate_immediately
|
|
111
|
-
SolidQueue.instrument(:immediate_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes:
|
|
112
|
-
|
|
114
|
+
SolidQueue.instrument(:immediate_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: configured_processes.keys) do
|
|
115
|
+
perform_immediate_termination
|
|
113
116
|
end
|
|
114
117
|
end
|
|
115
118
|
|
|
116
|
-
def
|
|
117
|
-
|
|
118
|
-
run_callbacks(:shutdown) do
|
|
119
|
-
stop_maintenance_task
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def sync_std_streams
|
|
125
|
-
STDOUT.sync = STDERR.sync = true
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def supervised_processes
|
|
129
|
-
forks.keys
|
|
119
|
+
def perform_graceful_termination
|
|
120
|
+
raise NotImplementedError
|
|
130
121
|
end
|
|
131
122
|
|
|
132
|
-
def
|
|
133
|
-
|
|
123
|
+
def perform_immediate_termination
|
|
124
|
+
raise NotImplementedError
|
|
134
125
|
end
|
|
135
126
|
|
|
136
|
-
def
|
|
137
|
-
|
|
127
|
+
def all_processes_terminated?
|
|
128
|
+
raise NotImplementedError
|
|
138
129
|
end
|
|
139
130
|
|
|
140
|
-
def
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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))
|
|
131
|
+
def shutdown
|
|
132
|
+
SolidQueue.instrument(:shutdown_process, process: self) do
|
|
133
|
+
run_callbacks(:shutdown) do
|
|
134
|
+
stop_maintenance_task
|
|
171
135
|
end
|
|
172
136
|
end
|
|
173
137
|
end
|
|
174
138
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# by some other worker.
|
|
178
|
-
def handle_claimed_jobs_by(terminated_fork, status)
|
|
179
|
-
if registered_process = SolidQueue::Process.find_by(name: terminated_fork.name)
|
|
180
|
-
error = Processes::ProcessExitError.new(status)
|
|
181
|
-
registered_process.fail_all_claimed_executions_with(error)
|
|
182
|
-
end
|
|
139
|
+
def set_procline
|
|
140
|
+
procline "supervising #{configured_processes.keys.join(", ")}"
|
|
183
141
|
end
|
|
184
142
|
|
|
185
|
-
def
|
|
186
|
-
|
|
143
|
+
def sync_std_streams
|
|
144
|
+
STDOUT.sync = STDERR.sync = true
|
|
187
145
|
end
|
|
188
146
|
end
|
|
189
147
|
end
|
data/lib/solid_queue/timer.rb
CHANGED
|
@@ -4,18 +4,18 @@ module SolidQueue
|
|
|
4
4
|
module Timer
|
|
5
5
|
extend self
|
|
6
6
|
|
|
7
|
-
def wait_until(timeout, condition
|
|
7
|
+
def wait_until(timeout, condition)
|
|
8
8
|
if timeout > 0
|
|
9
9
|
deadline = monotonic_time_now + timeout
|
|
10
10
|
|
|
11
11
|
while monotonic_time_now < deadline && !condition.call
|
|
12
12
|
sleep 0.1
|
|
13
|
-
|
|
13
|
+
yield if block_given?
|
|
14
14
|
end
|
|
15
15
|
else
|
|
16
16
|
while !condition.call
|
|
17
17
|
sleep 0.5
|
|
18
|
-
|
|
18
|
+
yield if block_given?
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
data/lib/solid_queue/version.rb
CHANGED
data/lib/solid_queue.rb
CHANGED
|
@@ -43,6 +43,14 @@ module SolidQueue
|
|
|
43
43
|
|
|
44
44
|
delegate :on_start, :on_stop, :on_exit, to: Supervisor
|
|
45
45
|
|
|
46
|
+
def schedule_recurring_task(key, **options)
|
|
47
|
+
RecurringTask.create_dynamic_task(key, **options)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def unschedule_recurring_task(key)
|
|
51
|
+
RecurringTask.delete_dynamic_task(key)
|
|
52
|
+
end
|
|
53
|
+
|
|
46
54
|
[ Dispatcher, Scheduler, Worker ].each do |process|
|
|
47
55
|
define_singleton_method(:"on_#{process.name.demodulize.downcase}_start") do |&block|
|
|
48
56
|
process.on_start(&block)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_queue
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.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:
|
|
11
|
+
date: 2026-03-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -72,14 +72,14 @@ dependencies:
|
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 1.11
|
|
75
|
+
version: '1.11'
|
|
76
76
|
type: :runtime
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 1.11
|
|
82
|
+
version: '1.11'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: thor
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -122,6 +122,20 @@ dependencies:
|
|
|
122
122
|
- - "~>"
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
124
|
version: '1.9'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: minitest
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '5.0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '5.0'
|
|
125
139
|
- !ruby/object:Gem::Dependency
|
|
126
140
|
name: mocha
|
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -140,16 +154,16 @@ dependencies:
|
|
|
140
154
|
name: puma
|
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
|
142
156
|
requirements:
|
|
143
|
-
- - "
|
|
157
|
+
- - "~>"
|
|
144
158
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0'
|
|
159
|
+
version: '7.0'
|
|
146
160
|
type: :development
|
|
147
161
|
prerelease: false
|
|
148
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
163
|
requirements:
|
|
150
|
-
- - "
|
|
164
|
+
- - "~>"
|
|
151
165
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0'
|
|
166
|
+
version: '7.0'
|
|
153
167
|
- !ruby/object:Gem::Dependency
|
|
154
168
|
name: mysql2
|
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -257,7 +271,6 @@ extra_rdoc_files: []
|
|
|
257
271
|
files:
|
|
258
272
|
- MIT-LICENSE
|
|
259
273
|
- README.md
|
|
260
|
-
- Rakefile
|
|
261
274
|
- UPGRADING.md
|
|
262
275
|
- app/jobs/solid_queue/recurring_job.rb
|
|
263
276
|
- app/models/solid_queue/blocked_execution.rb
|
|
@@ -298,11 +311,13 @@ files:
|
|
|
298
311
|
- lib/puma/plugin/solid_queue.rb
|
|
299
312
|
- lib/solid_queue.rb
|
|
300
313
|
- lib/solid_queue/app_executor.rb
|
|
314
|
+
- lib/solid_queue/async_supervisor.rb
|
|
301
315
|
- lib/solid_queue/cli.rb
|
|
302
316
|
- lib/solid_queue/configuration.rb
|
|
303
317
|
- lib/solid_queue/dispatcher.rb
|
|
304
318
|
- lib/solid_queue/dispatcher/concurrency_maintenance.rb
|
|
305
319
|
- lib/solid_queue/engine.rb
|
|
320
|
+
- lib/solid_queue/fork_supervisor.rb
|
|
306
321
|
- lib/solid_queue/lifecycle_hooks.rb
|
|
307
322
|
- lib/solid_queue/log_subscriber.rb
|
|
308
323
|
- lib/solid_queue/pool.rb
|
|
@@ -317,6 +332,7 @@ files:
|
|
|
317
332
|
- lib/solid_queue/processes/registrable.rb
|
|
318
333
|
- lib/solid_queue/processes/runnable.rb
|
|
319
334
|
- lib/solid_queue/processes/supervised.rb
|
|
335
|
+
- lib/solid_queue/processes/thread_terminated_error.rb
|
|
320
336
|
- lib/solid_queue/scheduler.rb
|
|
321
337
|
- lib/solid_queue/scheduler/recurring_schedule.rb
|
|
322
338
|
- lib/solid_queue/supervisor.rb
|
data/Rakefile
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
|
|
5
|
-
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
|
6
|
-
load "rails/tasks/engine.rake"
|
|
7
|
-
|
|
8
|
-
load "rails/tasks/statistics.rake"
|
|
9
|
-
|
|
10
|
-
require "bundler/gem_tasks"
|
|
11
|
-
require "rake/tasklib"
|
|
12
|
-
|
|
13
|
-
class TestHelpers < Rake::TaskLib
|
|
14
|
-
def initialize(databases)
|
|
15
|
-
@databases = databases
|
|
16
|
-
define
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def define
|
|
20
|
-
desc "Run tests for all databases (mysql, postgres, sqlite)"
|
|
21
|
-
task :test do
|
|
22
|
-
@databases.each { |database| run_test_for_database(database) }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
namespace :test do
|
|
26
|
-
@databases.each do |database|
|
|
27
|
-
desc "Run tests for #{database} database"
|
|
28
|
-
task database do
|
|
29
|
-
run_test_for_database(database)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def run_test_for_database(database)
|
|
38
|
-
sh("TARGET_DB=#{database} bin/setup")
|
|
39
|
-
sh("TARGET_DB=#{database} bin/rails test")
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
TestHelpers.new(%w[ mysql postgres sqlite ])
|