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.
- checksums.yaml +4 -4
- data/README.md +88 -14
- data/app/models/solid_queue/claimed_execution.rb +10 -3
- data/app/models/solid_queue/failed_execution.rb +37 -1
- data/app/models/solid_queue/job.rb +7 -0
- data/app/models/solid_queue/process/executor.rb +20 -0
- data/app/models/solid_queue/process/prunable.rb +15 -11
- data/app/models/solid_queue/process.rb +10 -9
- data/app/models/solid_queue/recurring_execution.rb +7 -3
- data/lib/puma/plugin/solid_queue.rb +39 -11
- data/lib/solid_queue/configuration.rb +18 -22
- data/lib/solid_queue/dispatcher/concurrency_maintenance.rb +1 -1
- data/lib/solid_queue/dispatcher/recurring_schedule.rb +4 -0
- data/lib/solid_queue/dispatcher/recurring_task.rb +14 -6
- data/lib/solid_queue/dispatcher.rb +7 -4
- data/lib/solid_queue/log_subscriber.rb +22 -13
- data/lib/solid_queue/processes/callbacks.rb +0 -7
- data/lib/solid_queue/processes/poller.rb +10 -11
- data/lib/solid_queue/processes/procline.rb +1 -1
- data/lib/solid_queue/processes/registrable.rb +9 -1
- data/lib/solid_queue/processes/runnable.rb +38 -7
- data/lib/solid_queue/processes/supervised.rb +2 -3
- data/lib/solid_queue/supervisor/async_supervisor.rb +44 -0
- data/lib/solid_queue/supervisor/fork_supervisor.rb +108 -0
- data/lib/solid_queue/supervisor/maintenance.rb +34 -0
- data/lib/solid_queue/{processes → supervisor}/pidfile.rb +2 -2
- data/lib/solid_queue/supervisor/pidfiled.rb +25 -0
- data/lib/solid_queue/supervisor/signals.rb +67 -0
- data/lib/solid_queue/supervisor.rb +32 -142
- data/lib/solid_queue/tasks.rb +1 -11
- data/lib/solid_queue/timer.rb +28 -0
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue/worker.rb +4 -5
- metadata +13 -5
- data/lib/solid_queue/processes/signals.rb +0 -69
@@ -1,9 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SolidQueue
|
4
|
-
class Dispatcher < Processes::
|
5
|
-
include Processes::Poller
|
6
|
-
|
4
|
+
class Dispatcher < Processes::Poller
|
7
5
|
attr_accessor :batch_size, :concurrency_maintenance, :recurring_schedule
|
8
6
|
|
9
7
|
after_boot :start_concurrency_maintenance, :load_recurring_schedule
|
@@ -13,10 +11,11 @@ module SolidQueue
|
|
13
11
|
options = options.dup.with_defaults(SolidQueue::Configuration::DISPATCHER_DEFAULTS)
|
14
12
|
|
15
13
|
@batch_size = options[:batch_size]
|
16
|
-
@polling_interval = options[:polling_interval]
|
17
14
|
|
18
15
|
@concurrency_maintenance = ConcurrencyMaintenance.new(options[:concurrency_maintenance_interval], options[:batch_size]) if options[:concurrency_maintenance]
|
19
16
|
@recurring_schedule = RecurringSchedule.new(options[:recurring_tasks])
|
17
|
+
|
18
|
+
super(**options)
|
20
19
|
end
|
21
20
|
|
22
21
|
def metadata
|
@@ -51,6 +50,10 @@ module SolidQueue
|
|
51
50
|
recurring_schedule.unload_tasks
|
52
51
|
end
|
53
52
|
|
53
|
+
def all_work_completed?
|
54
|
+
SolidQueue::ScheduledExecution.none? && recurring_schedule.empty?
|
55
|
+
end
|
56
|
+
|
54
57
|
def set_procline
|
55
58
|
procline "waiting"
|
56
59
|
end
|
@@ -7,6 +7,10 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
7
7
|
debug formatted_event(event, action: "Dispatch scheduled jobs", **event.payload.slice(:batch_size, :size))
|
8
8
|
end
|
9
9
|
|
10
|
+
def claim(event)
|
11
|
+
debug formatted_event(event, action: "Claim jobs", **event.payload.slice(:process_id, :job_ids, :claimed_job_ids, :size))
|
12
|
+
end
|
13
|
+
|
10
14
|
def release_many_claimed(event)
|
11
15
|
debug formatted_event(event, action: "Release claimed jobs", **event.payload.slice(:size))
|
12
16
|
end
|
@@ -40,13 +44,16 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def enqueue_recurring_task(event)
|
43
|
-
attributes = event.payload.slice(:task, :
|
47
|
+
attributes = event.payload.slice(:task, :active_job_id, :enqueue_error)
|
48
|
+
attributes[:at] = event.payload[:at]&.iso8601
|
44
49
|
|
45
|
-
if event.payload[:
|
50
|
+
if attributes[:active_job_id].nil? && event.payload[:skipped].nil?
|
51
|
+
error formatted_event(event, action: "Error enqueuing recurring task", **attributes)
|
52
|
+
elsif event.payload[:other_adapter]
|
46
53
|
debug formatted_event(event, action: "Enqueued recurring task outside Solid Queue", **attributes)
|
47
54
|
else
|
48
|
-
action =
|
49
|
-
|
55
|
+
action = event.payload[:skipped].present? ? "Skipped recurring task – already dispatched" : "Enqueued recurring task"
|
56
|
+
debug formatted_event(event, action: action, **attributes)
|
50
57
|
end
|
51
58
|
end
|
52
59
|
|
@@ -55,7 +62,8 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
55
62
|
|
56
63
|
attributes = {
|
57
64
|
pid: process.pid,
|
58
|
-
hostname: process.hostname
|
65
|
+
hostname: process.hostname,
|
66
|
+
process_id: process.process_id
|
59
67
|
}.merge(process.metadata)
|
60
68
|
|
61
69
|
info formatted_event(event, action: "Started #{process.kind}", **attributes)
|
@@ -66,15 +74,16 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
66
74
|
|
67
75
|
attributes = {
|
68
76
|
pid: process.pid,
|
69
|
-
hostname: process.hostname
|
77
|
+
hostname: process.hostname,
|
78
|
+
process_id: process.process_id
|
70
79
|
}.merge(process.metadata)
|
71
80
|
|
72
|
-
info formatted_event(event, action: "
|
81
|
+
info formatted_event(event, action: "Shutdown #{process.kind}", **attributes)
|
73
82
|
end
|
74
83
|
|
75
84
|
def register_process(event)
|
76
85
|
process_kind = event.payload[:kind]
|
77
|
-
attributes = event.payload.slice(:pid, :hostname)
|
86
|
+
attributes = event.payload.slice(:pid, :hostname, :process_id)
|
78
87
|
|
79
88
|
if error = event.payload[:error]
|
80
89
|
warn formatted_event(event, action: "Error registering #{process_kind}", **attributes.merge(error: formatted_error(error)))
|
@@ -90,9 +99,9 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
90
99
|
process_id: process.id,
|
91
100
|
pid: process.pid,
|
92
101
|
hostname: process.hostname,
|
93
|
-
last_heartbeat_at: process.last_heartbeat_at,
|
94
|
-
claimed_size:
|
95
|
-
pruned: event.payload
|
102
|
+
last_heartbeat_at: process.last_heartbeat_at.iso8601,
|
103
|
+
claimed_size: event.payload[:claimed_size],
|
104
|
+
pruned: event.payload[:pruned]
|
96
105
|
}
|
97
106
|
|
98
107
|
if error = event.payload[:error]
|
@@ -111,7 +120,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
111
120
|
end
|
112
121
|
|
113
122
|
def graceful_termination(event)
|
114
|
-
attributes = event.payload.slice(:supervisor_pid, :
|
123
|
+
attributes = event.payload.slice(:process_id, :supervisor_pid, :supervised_processes)
|
115
124
|
|
116
125
|
if event.payload[:shutdown_timeout_exceeded]
|
117
126
|
warn formatted_event(event, action: "Supervisor wasn't terminated gracefully - shutdown timeout exceeded", **attributes)
|
@@ -121,7 +130,7 @@ class SolidQueue::LogSubscriber < ActiveSupport::LogSubscriber
|
|
121
130
|
end
|
122
131
|
|
123
132
|
def immediate_termination(event)
|
124
|
-
info formatted_event(event, action: "Supervisor terminated immediately", **event.payload.slice(:supervisor_pid, :
|
133
|
+
info formatted_event(event, action: "Supervisor terminated immediately", **event.payload.slice(:process_id, :supervisor_pid, :supervised_processes))
|
125
134
|
end
|
126
135
|
|
127
136
|
def unhandled_signal_error(event)
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SolidQueue::Processes
|
4
|
-
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
4
|
+
class Poller < Base
|
7
5
|
include Runnable
|
8
6
|
|
9
|
-
|
10
|
-
|
7
|
+
attr_accessor :polling_interval
|
8
|
+
|
9
|
+
def initialize(polling_interval:, **options)
|
10
|
+
@polling_interval = polling_interval
|
11
11
|
end
|
12
12
|
|
13
13
|
def metadata
|
@@ -16,11 +16,7 @@ module SolidQueue::Processes
|
|
16
16
|
|
17
17
|
private
|
18
18
|
def run
|
19
|
-
|
20
|
-
@thread = Thread.new { start_loop }
|
21
|
-
else
|
22
|
-
start_loop
|
23
|
-
end
|
19
|
+
start_loop
|
24
20
|
end
|
25
21
|
|
26
22
|
def start_loop
|
@@ -43,8 +39,11 @@ module SolidQueue::Processes
|
|
43
39
|
raise NotImplementedError
|
44
40
|
end
|
45
41
|
|
42
|
+
def shutdown
|
43
|
+
end
|
44
|
+
|
46
45
|
def with_polling_volume
|
47
|
-
if SolidQueue.silence_polling?
|
46
|
+
if SolidQueue.silence_polling? && ActiveRecord::Base.logger
|
48
47
|
ActiveRecord::Base.logger.silence { yield }
|
49
48
|
else
|
50
49
|
yield
|
@@ -5,7 +5,7 @@ module SolidQueue::Processes
|
|
5
5
|
# Sets the procline ($0)
|
6
6
|
# solid-queue-supervisor(0.1.0): <string>
|
7
7
|
def procline(string)
|
8
|
-
$0 = "solid-queue-#{self.class.name.demodulize.
|
8
|
+
$0 = "solid-queue-#{self.class.name.demodulize.underscore.dasherize}(#{SolidQueue::VERSION}): #{string}"
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -11,12 +11,16 @@ module SolidQueue::Processes
|
|
11
11
|
after_shutdown :deregister
|
12
12
|
end
|
13
13
|
|
14
|
+
def process_id
|
15
|
+
process&.id
|
16
|
+
end
|
17
|
+
|
14
18
|
private
|
15
19
|
attr_accessor :process
|
16
20
|
|
17
21
|
def register
|
18
22
|
@process = SolidQueue::Process.register \
|
19
|
-
kind:
|
23
|
+
kind: kind,
|
20
24
|
pid: pid,
|
21
25
|
hostname: hostname,
|
22
26
|
supervisor: try(:supervisor),
|
@@ -36,6 +40,10 @@ module SolidQueue::Processes
|
|
36
40
|
wrap_in_app_executor { heartbeat }
|
37
41
|
end
|
38
42
|
|
43
|
+
@heartbeat_task.add_observer do |_, _, error|
|
44
|
+
handle_thread_error(error) if error
|
45
|
+
end
|
46
|
+
|
39
47
|
@heartbeat_task.execute
|
40
48
|
end
|
41
49
|
|
@@ -7,20 +7,32 @@ module SolidQueue::Processes
|
|
7
7
|
attr_writer :mode
|
8
8
|
|
9
9
|
def start
|
10
|
-
@
|
10
|
+
@stopped = false
|
11
11
|
|
12
12
|
SolidQueue.instrument(:start_process, process: self) do
|
13
13
|
run_callbacks(:boot) { boot }
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
if running_async?
|
17
|
+
@thread = create_thread { run }
|
18
|
+
else
|
19
|
+
run
|
20
|
+
end
|
17
21
|
end
|
18
22
|
|
19
23
|
def stop
|
20
|
-
@
|
24
|
+
@stopped = true
|
21
25
|
@thread&.join
|
22
26
|
end
|
23
27
|
|
28
|
+
def name
|
29
|
+
@name ||= [ kind.downcase, SecureRandom.hex(6) ].join("-")
|
30
|
+
end
|
31
|
+
|
32
|
+
def alive?
|
33
|
+
!running_async? || @thread.alive?
|
34
|
+
end
|
35
|
+
|
24
36
|
private
|
25
37
|
DEFAULT_MODE = :async
|
26
38
|
|
@@ -29,22 +41,22 @@ module SolidQueue::Processes
|
|
29
41
|
end
|
30
42
|
|
31
43
|
def boot
|
32
|
-
if
|
44
|
+
if running_as_fork?
|
33
45
|
register_signal_handlers
|
34
46
|
set_procline
|
35
47
|
end
|
36
48
|
end
|
37
49
|
|
38
50
|
def shutting_down?
|
39
|
-
|
51
|
+
stopped? || (running_as_fork? && supervisor_went_away?) || finished?
|
40
52
|
end
|
41
53
|
|
42
54
|
def run
|
43
55
|
raise NotImplementedError
|
44
56
|
end
|
45
57
|
|
46
|
-
def
|
47
|
-
@
|
58
|
+
def stopped?
|
59
|
+
@stopped
|
48
60
|
end
|
49
61
|
|
50
62
|
def finished?
|
@@ -61,5 +73,24 @@ module SolidQueue::Processes
|
|
61
73
|
def running_inline?
|
62
74
|
mode.inline?
|
63
75
|
end
|
76
|
+
|
77
|
+
def running_async?
|
78
|
+
mode.async?
|
79
|
+
end
|
80
|
+
|
81
|
+
def running_as_fork?
|
82
|
+
mode.fork?
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def create_thread(&block)
|
87
|
+
Thread.new do
|
88
|
+
Thread.current.name = name
|
89
|
+
block.call
|
90
|
+
rescue Exception => exception
|
91
|
+
handle_thread_error(exception)
|
92
|
+
raise
|
93
|
+
end
|
94
|
+
end
|
64
95
|
end
|
65
96
|
end
|
@@ -9,7 +9,6 @@ module SolidQueue::Processes
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def supervised_by(process)
|
12
|
-
self.mode = :supervised
|
13
12
|
@supervisor = process
|
14
13
|
end
|
15
14
|
|
@@ -19,11 +18,11 @@ module SolidQueue::Processes
|
|
19
18
|
end
|
20
19
|
|
21
20
|
def supervisor_went_away?
|
22
|
-
supervised? && supervisor
|
21
|
+
supervised? && supervisor.pid != ::Process.ppid
|
23
22
|
end
|
24
23
|
|
25
24
|
def supervised?
|
26
|
-
|
25
|
+
supervisor.present?
|
27
26
|
end
|
28
27
|
|
29
28
|
def register_signal_handlers
|
@@ -0,0 +1,44 @@
|
|
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
|
+
configured_process.supervised_by process
|
27
|
+
configured_process.start
|
28
|
+
|
29
|
+
threads[configured_process.name] = configured_process
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop_threads
|
33
|
+
stop_threads = threads.values.map do |thr|
|
34
|
+
Thread.new { thr.stop }
|
35
|
+
end
|
36
|
+
|
37
|
+
stop_threads.each { |thr| thr.join(SolidQueue.shutdown_timeout) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def all_threads_terminated?
|
41
|
+
threads.values.none?(&:alive?)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Supervisor::ForkSupervisor < Supervisor
|
5
|
+
include Signals, Pidfiled
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
@forks = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def kind
|
13
|
+
"Supervisor(fork)"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_reader :forks
|
18
|
+
|
19
|
+
def supervise
|
20
|
+
loop do
|
21
|
+
break if stopped?
|
22
|
+
|
23
|
+
procline "supervising #{forks.keys.join(", ")}"
|
24
|
+
process_signal_queue
|
25
|
+
|
26
|
+
unless stopped?
|
27
|
+
reap_and_replace_terminated_forks
|
28
|
+
interruptible_sleep(1.second)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
shutdown
|
33
|
+
end
|
34
|
+
|
35
|
+
def start_process(configured_process)
|
36
|
+
configured_process.supervised_by process
|
37
|
+
configured_process.mode = :fork
|
38
|
+
|
39
|
+
pid = fork do
|
40
|
+
configured_process.start
|
41
|
+
end
|
42
|
+
|
43
|
+
forks[pid] = configured_process
|
44
|
+
end
|
45
|
+
|
46
|
+
def terminate_gracefully
|
47
|
+
SolidQueue.instrument(:graceful_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: forks.keys) do |payload|
|
48
|
+
term_forks
|
49
|
+
|
50
|
+
Timer.wait_until(SolidQueue.shutdown_timeout, -> { all_forks_terminated? }) do
|
51
|
+
reap_terminated_forks
|
52
|
+
end
|
53
|
+
|
54
|
+
unless all_forks_terminated?
|
55
|
+
payload[:shutdown_timeout_exceeded] = true
|
56
|
+
terminate_immediately
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def terminate_immediately
|
62
|
+
SolidQueue.instrument(:immediate_termination, process_id: process_id, supervisor_pid: ::Process.pid, supervised_processes: forks.keys) do
|
63
|
+
quit_forks
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def term_forks
|
68
|
+
signal_processes(forks.keys, :TERM)
|
69
|
+
end
|
70
|
+
|
71
|
+
def quit_forks
|
72
|
+
signal_processes(forks.keys, :QUIT)
|
73
|
+
end
|
74
|
+
|
75
|
+
def reap_and_replace_terminated_forks
|
76
|
+
loop do
|
77
|
+
pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
|
78
|
+
break unless pid
|
79
|
+
|
80
|
+
replace_fork(pid, status)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def reap_terminated_forks
|
85
|
+
loop do
|
86
|
+
pid, status = ::Process.waitpid2(-1, ::Process::WNOHANG)
|
87
|
+
break unless pid
|
88
|
+
|
89
|
+
forks.delete(pid)
|
90
|
+
end
|
91
|
+
rescue SystemCallError
|
92
|
+
# All children already reaped
|
93
|
+
end
|
94
|
+
|
95
|
+
def replace_fork(pid, status)
|
96
|
+
SolidQueue.instrument(:replace_fork, supervisor_pid: ::Process.pid, pid: pid, status: status) do |payload|
|
97
|
+
if supervised_fork = forks.delete(pid)
|
98
|
+
payload[:fork] = supervised_fork
|
99
|
+
start_process(supervised_fork)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def all_forks_terminated?
|
105
|
+
forks.empty?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SolidQueue
|
2
|
+
module Supervisor::Maintenance
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
after_boot :release_orphaned_executions
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def launch_maintenance_task
|
11
|
+
@maintenance_task = Concurrent::TimerTask.new(run_now: true, execution_interval: SolidQueue.process_alive_threshold) do
|
12
|
+
prune_dead_processes
|
13
|
+
end
|
14
|
+
|
15
|
+
@maintenance_task.add_observer do |_, _, error|
|
16
|
+
handle_thread_error(error) if error
|
17
|
+
end
|
18
|
+
|
19
|
+
@maintenance_task.execute
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop_maintenance_task
|
23
|
+
@maintenance_task&.shutdown
|
24
|
+
end
|
25
|
+
|
26
|
+
def prune_dead_processes
|
27
|
+
wrap_in_app_executor { SolidQueue::Process.prune }
|
28
|
+
end
|
29
|
+
|
30
|
+
def release_orphaned_executions
|
31
|
+
wrap_in_app_executor { SolidQueue::ClaimedExecution.orphaned.release_all }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Supervisor
|
5
|
+
module Pidfiled
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before_boot :setup_pidfile
|
10
|
+
after_shutdown :delete_pidfile
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def setup_pidfile
|
15
|
+
if path = SolidQueue.supervisor_pidfile
|
16
|
+
@pidfile = Pidfile.new(path).tap(&:setup)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_pidfile
|
21
|
+
@pidfile&.delete
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueue
|
4
|
+
class Supervisor
|
5
|
+
module Signals
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before_boot :register_signal_handlers
|
10
|
+
after_shutdown :restore_default_signal_handlers
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
SIGNALS = %i[ QUIT INT TERM ]
|
15
|
+
|
16
|
+
def register_signal_handlers
|
17
|
+
SIGNALS.each do |signal|
|
18
|
+
trap(signal) do
|
19
|
+
signal_queue << signal
|
20
|
+
interrupt
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def restore_default_signal_handlers
|
26
|
+
SIGNALS.each do |signal|
|
27
|
+
trap(signal, :DEFAULT)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_signal_queue
|
32
|
+
while signal = signal_queue.shift
|
33
|
+
handle_signal(signal)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_signal(signal)
|
38
|
+
case signal
|
39
|
+
when :TERM, :INT
|
40
|
+
stop
|
41
|
+
terminate_gracefully
|
42
|
+
when :QUIT
|
43
|
+
stop
|
44
|
+
terminate_immediately
|
45
|
+
else
|
46
|
+
SolidQueue.instrument :unhandled_signal_error, signal: signal
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def signal_processes(pids, signal)
|
51
|
+
pids.each do |pid|
|
52
|
+
signal_process pid, signal
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def signal_process(pid, signal)
|
57
|
+
::Process.kill signal, pid
|
58
|
+
rescue Errno::ESRCH
|
59
|
+
# Ignore, process died before
|
60
|
+
end
|
61
|
+
|
62
|
+
def signal_queue
|
63
|
+
@signal_queue ||= []
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|