solid_queue 0.3.3 → 0.4.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 +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
|