solid_queue 0.3.3 → 0.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -14
  3. data/app/models/solid_queue/claimed_execution.rb +10 -3
  4. data/app/models/solid_queue/failed_execution.rb +37 -1
  5. data/app/models/solid_queue/job.rb +7 -0
  6. data/app/models/solid_queue/process/executor.rb +20 -0
  7. data/app/models/solid_queue/process/prunable.rb +15 -11
  8. data/app/models/solid_queue/process.rb +10 -9
  9. data/app/models/solid_queue/recurring_execution.rb +7 -3
  10. data/lib/puma/plugin/solid_queue.rb +39 -11
  11. data/lib/solid_queue/configuration.rb +18 -22
  12. data/lib/solid_queue/dispatcher/concurrency_maintenance.rb +1 -1
  13. data/lib/solid_queue/dispatcher/recurring_schedule.rb +4 -0
  14. data/lib/solid_queue/dispatcher/recurring_task.rb +14 -6
  15. data/lib/solid_queue/dispatcher.rb +7 -4
  16. data/lib/solid_queue/log_subscriber.rb +22 -13
  17. data/lib/solid_queue/processes/callbacks.rb +0 -7
  18. data/lib/solid_queue/processes/poller.rb +10 -11
  19. data/lib/solid_queue/processes/procline.rb +1 -1
  20. data/lib/solid_queue/processes/registrable.rb +9 -1
  21. data/lib/solid_queue/processes/runnable.rb +38 -7
  22. data/lib/solid_queue/processes/supervised.rb +2 -3
  23. data/lib/solid_queue/supervisor/async_supervisor.rb +44 -0
  24. data/lib/solid_queue/supervisor/fork_supervisor.rb +108 -0
  25. data/lib/solid_queue/supervisor/maintenance.rb +34 -0
  26. data/lib/solid_queue/{processes → supervisor}/pidfile.rb +2 -2
  27. data/lib/solid_queue/supervisor/pidfiled.rb +25 -0
  28. data/lib/solid_queue/supervisor/signals.rb +67 -0
  29. data/lib/solid_queue/supervisor.rb +32 -142
  30. data/lib/solid_queue/tasks.rb +1 -11
  31. data/lib/solid_queue/timer.rb +28 -0
  32. data/lib/solid_queue/version.rb +1 -1
  33. data/lib/solid_queue/worker.rb +4 -5
  34. metadata +13 -5
  35. data/lib/solid_queue/processes/signals.rb +0 -69
@@ -2,182 +2,72 @@
2
2
 
3
3
  module SolidQueue
4
4
  class Supervisor < Processes::Base
5
- include Processes::Signals
5
+ include Maintenance
6
6
 
7
7
  class << self
8
- def start(mode: :work, load_configuration_from: nil)
8
+ def start(mode: :fork, load_configuration_from: nil)
9
9
  SolidQueue.supervisor = true
10
10
  configuration = Configuration.new(mode: mode, load_from: load_configuration_from)
11
11
 
12
- new(*configuration.processes).start
12
+ klass = mode == :fork ? ForkSupervisor : AsyncSupervisor
13
+ klass.new(configuration).tap(&:start)
13
14
  end
14
15
  end
15
16
 
16
- def initialize(*configured_processes)
17
- @configured_processes = Array(configured_processes)
18
- @forks = {}
17
+ def initialize(configuration)
18
+ @configuration = configuration
19
19
  end
20
20
 
21
21
  def start
22
- run_callbacks(:boot) { boot }
22
+ boot
23
+
24
+ start_processes
25
+ launch_maintenance_task
23
26
 
24
- start_forks
25
- launch_process_prune
26
27
  supervise
27
- rescue Processes::GracefulTerminationRequested
28
- graceful_termination
29
- rescue Processes::ImmediateTerminationRequested
30
- immediate_termination
31
- ensure
32
- run_callbacks(:shutdown) { shutdown }
28
+ end
29
+
30
+ def stop
31
+ @stopped = true
33
32
  end
34
33
 
35
34
  private
36
- attr_reader :configured_processes, :forks
35
+ attr_reader :configuration
37
36
 
38
37
  def boot
39
- sync_std_streams
40
- setup_pidfile
41
- register_signal_handlers
42
- end
43
-
44
- def supervise
45
- loop do
46
- procline "supervising #{forks.keys.join(", ")}"
47
-
48
- process_signal_queue
49
- reap_and_replace_terminated_forks
50
- interruptible_sleep(1.second)
51
- end
52
- end
53
-
54
- def sync_std_streams
55
- STDOUT.sync = STDERR.sync = true
56
- end
57
-
58
- def setup_pidfile
59
- @pidfile = if SolidQueue.supervisor_pidfile
60
- Processes::Pidfile.new(SolidQueue.supervisor_pidfile).tap(&:setup)
61
- end
62
- end
63
-
64
- def start_forks
65
- configured_processes.each { |configured_process| start_fork(configured_process) }
66
- end
67
-
68
- def launch_process_prune
69
- @prune_task = Concurrent::TimerTask.new(run_now: true, execution_interval: SolidQueue.process_alive_threshold) { prune_dead_processes }
70
- @prune_task.execute
71
- end
72
-
73
- def shutdown
74
- stop_process_prune
75
- restore_default_signal_handlers
76
- delete_pidfile
77
- end
78
-
79
- def graceful_termination
80
- SolidQueue.instrument(:graceful_termination, supervisor_pid: ::Process.pid, supervised_pids: forks.keys) do |payload|
81
- term_forks
82
-
83
- wait_until(SolidQueue.shutdown_timeout, -> { all_forks_terminated? }) do
84
- reap_terminated_forks
85
- end
86
-
87
- unless all_forks_terminated?
88
- payload[:shutdown_timeout_exceeded] = true
89
- immediate_termination
38
+ SolidQueue.instrument(:start_process, process: self) do
39
+ run_callbacks(:boot) do
40
+ @stopped = false
41
+ sync_std_streams
90
42
  end
91
43
  end
92
44
  end
93
45
 
94
- def immediate_termination
95
- SolidQueue.instrument(:immediate_termination, supervisor_pid: ::Process.pid, supervised_pids: forks.keys) do
96
- quit_forks
97
- end
46
+ def start_processes
47
+ configuration.processes.each { |configured_process| start_process(configured_process) }
98
48
  end
99
49
 
100
- def term_forks
101
- signal_processes(forks.keys, :TERM)
50
+ def stopped?
51
+ @stopped
102
52
  end
103
53
 
104
- def quit_forks
105
- signal_processes(forks.keys, :QUIT)
106
- end
107
-
108
- def stop_process_prune
109
- @prune_task&.shutdown
110
- end
111
-
112
- def delete_pidfile
113
- @pidfile&.delete
114
- end
115
-
116
- def prune_dead_processes
117
- wrap_in_app_executor { SolidQueue::Process.prune }
118
- end
119
-
120
- def start_fork(configured_process)
121
- configured_process.supervised_by process
122
-
123
- pid = fork do
124
- configured_process.start
125
- end
126
-
127
- forks[pid] = configured_process
128
- end
129
-
130
- def reap_and_replace_terminated_forks
131
- loop do
132
- pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
133
- break unless pid
134
-
135
- replace_fork(pid, status)
136
- end
137
- end
138
-
139
- def reap_terminated_forks
140
- loop do
141
- pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
142
- break unless pid
143
-
144
- forks.delete(pid)
145
- end
146
- rescue SystemCallError
147
- # All children already reaped
148
- end
149
-
150
- def replace_fork(pid, status)
151
- SolidQueue.instrument(:replace_fork, supervisor_pid: ::Process.pid, pid: pid, status: status) do |payload|
152
- if supervised_fork = forks.delete(pid)
153
- payload[:fork] = supervised_fork
154
- start_fork(supervised_fork)
155
- end
156
- end
54
+ def supervise
157
55
  end
158
56
 
159
- def all_forks_terminated?
160
- forks.empty?
57
+ def start_process(configured_process)
58
+ raise NotImplementedError
161
59
  end
162
60
 
163
- def wait_until(timeout, condition, &block)
164
- if timeout > 0
165
- deadline = monotonic_time_now + timeout
166
-
167
- while monotonic_time_now < deadline && !condition.call
168
- sleep 0.1
169
- block.call
170
- end
171
- else
172
- while !condition.call
173
- sleep 0.5
174
- block.call
61
+ def shutdown
62
+ SolidQueue.instrument(:shutdown_process, process: self) do
63
+ run_callbacks(:shutdown) do
64
+ stop_maintenance_task
175
65
  end
176
66
  end
177
67
  end
178
68
 
179
- def monotonic_time_now
180
- ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
69
+ def sync_std_streams
70
+ STDOUT.sync = STDERR.sync = true
181
71
  end
182
72
  end
183
73
  end
@@ -1,16 +1,6 @@
1
1
  namespace :solid_queue do
2
2
  desc "start solid_queue supervisor to dispatch and process jobs"
3
3
  task start: :environment do
4
- SolidQueue::Supervisor.start(mode: :all)
5
- end
6
-
7
- desc "start solid_queue supervisor to process jobs"
8
- task work: :environment do
9
- SolidQueue::Supervisor.start(mode: :work)
10
- end
11
-
12
- desc "start solid_queue dispatcher to enqueue scheduled jobs"
13
- task dispatch: :environment do
14
- SolidQueue::Supervisor.start(mode: :dispatch)
4
+ SolidQueue::Supervisor.start
15
5
  end
16
6
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueue
4
+ module Timer
5
+ extend self
6
+
7
+ def wait_until(timeout, condition, &block)
8
+ if timeout > 0
9
+ deadline = monotonic_time_now + timeout
10
+
11
+ while monotonic_time_now < deadline && !condition.call
12
+ sleep 0.1
13
+ block.call
14
+ end
15
+ else
16
+ while !condition.call
17
+ sleep 0.5
18
+ block.call
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+ def monotonic_time_now
25
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module SolidQueue
2
- VERSION = "0.3.3"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,17 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueue
4
- class Worker < Processes::Base
5
- include Processes::Poller
6
-
4
+ class Worker < Processes::Poller
7
5
  attr_accessor :queues, :pool
8
6
 
9
7
  def initialize(**options)
10
8
  options = options.dup.with_defaults(SolidQueue::Configuration::WORKER_DEFAULTS)
11
9
 
12
- @polling_interval = options[:polling_interval]
13
10
  @queues = Array(options[:queues])
14
11
  @pool = Pool.new(options[:threads], on_idle: -> { wake_up })
12
+
13
+ super(**options)
15
14
  end
16
15
 
17
16
  def metadata
@@ -31,7 +30,7 @@ module SolidQueue
31
30
 
32
31
  def claim_executions
33
32
  with_polling_volume do
34
- SolidQueue::ReadyExecution.claim(queues, pool.idle_threads, process.id)
33
+ SolidQueue::ReadyExecution.claim(queues, pool.idle_threads, process_id)
35
34
  end
36
35
  end
37
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: 0.3.3
4
+ version: 0.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: 2024-06-11 00:00:00.000000000 Z
11
+ date: 2024-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -203,6 +203,7 @@ files:
203
203
  - app/models/solid_queue/job/schedulable.rb
204
204
  - app/models/solid_queue/pause.rb
205
205
  - app/models/solid_queue/process.rb
206
+ - app/models/solid_queue/process/executor.rb
206
207
  - app/models/solid_queue/process/prunable.rb
207
208
  - app/models/solid_queue/queue.rb
208
209
  - app/models/solid_queue/queue_selector.rb
@@ -234,15 +235,20 @@ files:
234
235
  - lib/solid_queue/processes/base.rb
235
236
  - lib/solid_queue/processes/callbacks.rb
236
237
  - lib/solid_queue/processes/interruptible.rb
237
- - lib/solid_queue/processes/pidfile.rb
238
238
  - lib/solid_queue/processes/poller.rb
239
239
  - lib/solid_queue/processes/procline.rb
240
240
  - lib/solid_queue/processes/registrable.rb
241
241
  - lib/solid_queue/processes/runnable.rb
242
- - lib/solid_queue/processes/signals.rb
243
242
  - lib/solid_queue/processes/supervised.rb
244
243
  - lib/solid_queue/supervisor.rb
244
+ - lib/solid_queue/supervisor/async_supervisor.rb
245
+ - lib/solid_queue/supervisor/fork_supervisor.rb
246
+ - lib/solid_queue/supervisor/maintenance.rb
247
+ - lib/solid_queue/supervisor/pidfile.rb
248
+ - lib/solid_queue/supervisor/pidfiled.rb
249
+ - lib/solid_queue/supervisor/signals.rb
245
250
  - lib/solid_queue/tasks.rb
251
+ - lib/solid_queue/timer.rb
246
252
  - lib/solid_queue/version.rb
247
253
  - lib/solid_queue/worker.rb
248
254
  homepage: https://github.com/rails/solid_queue
@@ -251,7 +257,9 @@ licenses:
251
257
  metadata:
252
258
  homepage_uri: https://github.com/rails/solid_queue
253
259
  source_code_uri: https://github.com/rails/solid_queue
254
- post_install_message:
260
+ post_install_message: |
261
+ Upgrading to Solid Queue 0.4.x? There are some breaking changes about how Solid Queue is started. Check
262
+ https://github.com/rails/solid_cache/blob/main/UPGRADING.md for upgrade instructions.
255
263
  rdoc_options: []
256
264
  require_paths:
257
265
  - lib
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SolidQueue::Processes
4
- class GracefulTerminationRequested < Interrupt; end
5
- class ImmediateTerminationRequested < Interrupt; end
6
-
7
- module Signals
8
- extend ActiveSupport::Concern
9
-
10
- private
11
- SIGNALS = %i[ QUIT INT TERM ]
12
-
13
- def register_signal_handlers
14
- SIGNALS.each do |signal|
15
- trap(signal) do
16
- signal_queue << signal
17
- interrupt
18
- end
19
- end
20
- end
21
-
22
- def restore_default_signal_handlers
23
- SIGNALS.each do |signal|
24
- trap(signal, :DEFAULT)
25
- end
26
- end
27
-
28
- def process_signal_queue
29
- while signal = signal_queue.shift
30
- handle_signal(signal)
31
- end
32
- end
33
-
34
- def handle_signal(signal)
35
- case signal
36
- when :TERM, :INT
37
- request_graceful_termination
38
- when :QUIT
39
- request_immediate_termination
40
- else
41
- SolidQueue.instrument :unhandled_signal_error, signal: signal
42
- end
43
- end
44
-
45
- def request_graceful_termination
46
- raise GracefulTerminationRequested
47
- end
48
-
49
- def request_immediate_termination
50
- raise ImmediateTerminationRequested
51
- end
52
-
53
- def signal_processes(pids, signal)
54
- pids.each do |pid|
55
- signal_process pid, signal
56
- end
57
- end
58
-
59
- def signal_process(pid, signal)
60
- ::Process.kill signal, pid
61
- rescue Errno::ESRCH
62
- # Ignore, process died before
63
- end
64
-
65
- def signal_queue
66
- @signal_queue ||= []
67
- end
68
- end
69
- end