shoryuken 2.1.0 → 3.0.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/.codeclimate.yml +5 -8
- data/.rubocop.yml +8 -2
- data/.travis.yml +5 -0
- data/CHANGELOG.md +88 -11
- data/README.md +20 -57
- data/Rakefile +0 -1
- data/bin/cli/base.rb +42 -0
- data/bin/cli/sqs.rb +188 -0
- data/bin/shoryuken +47 -9
- data/examples/default_worker.rb +1 -1
- data/lib/shoryuken/client.rb +3 -15
- data/lib/shoryuken/default_worker_registry.rb +9 -5
- data/lib/shoryuken/environment_loader.rb +11 -40
- data/lib/shoryuken/fetcher.rb +22 -53
- data/lib/shoryuken/launcher.rb +5 -29
- data/lib/shoryuken/manager.rb +72 -190
- data/lib/shoryuken/message.rb +4 -13
- data/lib/shoryuken/middleware/chain.rb +1 -18
- data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +13 -14
- data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +29 -20
- data/lib/shoryuken/polling.rb +204 -0
- data/lib/shoryuken/processor.rb +6 -14
- data/lib/shoryuken/queue.rb +1 -3
- data/lib/shoryuken/runner.rb +143 -0
- data/lib/shoryuken/util.rb +0 -8
- data/lib/shoryuken/version.rb +1 -1
- data/lib/shoryuken/worker.rb +1 -1
- data/lib/shoryuken.rb +77 -55
- data/shoryuken.gemspec +6 -6
- data/spec/integration/launcher_spec.rb +4 -3
- data/spec/shoryuken/client_spec.rb +2 -45
- data/spec/shoryuken/default_worker_registry_spec.rb +12 -10
- data/spec/shoryuken/environment_loader_spec.rb +34 -0
- data/spec/shoryuken/fetcher_spec.rb +18 -52
- data/spec/shoryuken/manager_spec.rb +56 -97
- data/spec/shoryuken/middleware/chain_spec.rb +0 -24
- data/spec/shoryuken/middleware/server/auto_delete_spec.rb +2 -2
- data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +7 -3
- data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +56 -33
- data/spec/shoryuken/polling_spec.rb +239 -0
- data/spec/shoryuken/processor_spec.rb +5 -5
- data/spec/shoryuken/{cli_spec.rb → runner_spec.rb} +8 -22
- data/spec/shoryuken_spec.rb +13 -1
- data/spec/spec_helper.rb +3 -18
- data/test_workers/endless_interruptive_worker.rb +41 -0
- data/test_workers/endless_uninterruptive_worker.rb +44 -0
- metadata +32 -34
- data/lib/shoryuken/aws_config.rb +0 -64
- data/lib/shoryuken/cli.rb +0 -215
- data/lib/shoryuken/sns_arn.rb +0 -27
- data/lib/shoryuken/topic.rb +0 -17
- data/spec/shoryuken/sns_arn_spec.rb +0 -42
- data/spec/shoryuken/topic_spec.rb +0 -32
- data/spec/shoryuken_endpoint.yml +0 -6
|
@@ -1,33 +1,32 @@
|
|
|
1
|
-
require 'celluloid/current' unless defined?(Celluloid)
|
|
2
|
-
|
|
3
1
|
module Shoryuken
|
|
4
2
|
module Middleware
|
|
5
3
|
module Server
|
|
6
4
|
class AutoExtendVisibility
|
|
5
|
+
include Util
|
|
6
|
+
|
|
7
7
|
EXTEND_UPFRONT_SECONDS = 5
|
|
8
8
|
|
|
9
9
|
def call(worker, queue, sqs_msg, body)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
yield
|
|
13
|
-
ensure
|
|
14
|
-
if timer
|
|
15
|
-
timer.cancel
|
|
16
|
-
@visibility_extender.terminate
|
|
17
|
-
end
|
|
10
|
+
if sqs_msg.is_a?(Array)
|
|
11
|
+
logger.warn { "Auto extend visibility isn't supported for batch workers" }
|
|
12
|
+
return yield
|
|
18
13
|
end
|
|
14
|
+
|
|
15
|
+
timer = auto_visibility_timer(worker, queue, sqs_msg, body)
|
|
16
|
+
yield
|
|
17
|
+
ensure
|
|
18
|
+
timer.kill if timer
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
23
|
class MessageVisibilityExtender
|
|
24
|
-
include Celluloid
|
|
25
24
|
include Util
|
|
26
25
|
|
|
27
26
|
def auto_extend(worker, queue, sqs_msg, body)
|
|
28
27
|
queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
Concurrent::TimerTask.new(execution_interval: queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
|
|
31
30
|
begin
|
|
32
31
|
logger.debug do
|
|
33
32
|
"Extending message #{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
|
|
@@ -48,8 +47,8 @@ module Shoryuken
|
|
|
48
47
|
|
|
49
48
|
def auto_visibility_timer(worker, queue, sqs_msg, body)
|
|
50
49
|
return unless worker.class.auto_visibility_timeout?
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
|
|
51
|
+
MessageVisibilityExtender.new.auto_extend(worker, queue, sqs_msg, body).tap(&:execute)
|
|
53
52
|
end
|
|
54
53
|
end
|
|
55
54
|
end
|
|
@@ -5,12 +5,17 @@ module Shoryuken
|
|
|
5
5
|
include Util
|
|
6
6
|
|
|
7
7
|
def call(worker, queue, sqs_msg, body)
|
|
8
|
+
if sqs_msg.is_a?(Array)
|
|
9
|
+
logger.warn { "Exponential backoff isn't supported for batch workers" }
|
|
10
|
+
return yield
|
|
11
|
+
end
|
|
12
|
+
|
|
8
13
|
started_at = Time.now
|
|
9
14
|
yield
|
|
10
15
|
rescue
|
|
11
|
-
retry_intervals =
|
|
16
|
+
retry_intervals = worker.class.get_shoryuken_options['retry_intervals']
|
|
12
17
|
|
|
13
|
-
if retry_intervals.
|
|
18
|
+
if retry_intervals.nil? || !handle_failure(sqs_msg, started_at, retry_intervals)
|
|
14
19
|
# Re-raise the exception if the job is not going to be exponential backoff retried.
|
|
15
20
|
# This allows custom middleware (like exception notifiers) to be aware of the unhandled failure.
|
|
16
21
|
raise
|
|
@@ -19,28 +24,32 @@ module Shoryuken
|
|
|
19
24
|
|
|
20
25
|
private
|
|
21
26
|
|
|
22
|
-
def
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
attempts =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# We calculate the maximum timeout by subtracting the amount of time since the receipt of the message.
|
|
35
|
-
#
|
|
36
|
-
# From the docs: "Amazon SQS restarts the timeout period using the new value."
|
|
37
|
-
# http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html#AboutVT-extending-message-visibility-timeout
|
|
38
|
-
max_timeout = 43200 - (Time.now - started_at).ceil - 1
|
|
27
|
+
def get_interval(retry_intervals, attempts)
|
|
28
|
+
return retry_intervals.call(attempts) if retry_intervals.respond_to?(:call)
|
|
29
|
+
|
|
30
|
+
if attempts <= (retry_intervals = Array(retry_intervals)).size
|
|
31
|
+
retry_intervals[attempts - 1]
|
|
32
|
+
else
|
|
33
|
+
retry_intervals.last
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def next_visibility_timeout(interval, started_at)
|
|
38
|
+
max_timeout = 43_200 - (Time.now - started_at).ceil - 1
|
|
39
39
|
interval = max_timeout if interval > max_timeout
|
|
40
|
+
interval.to_i
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def handle_failure(sqs_msg, started_at, retry_intervals)
|
|
44
|
+
receive_count = sqs_msg.attributes['ApproximateReceiveCount'].to_i
|
|
45
|
+
|
|
46
|
+
return false unless (interval = get_interval(retry_intervals, receive_count))
|
|
40
47
|
|
|
41
|
-
sqs_msg.change_visibility(visibility_timeout: interval.to_i)
|
|
48
|
+
sqs_msg.change_visibility(visibility_timeout: next_visibility_timeout(interval.to_i, started_at))
|
|
42
49
|
|
|
43
50
|
logger.info { "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds." }
|
|
51
|
+
|
|
52
|
+
true
|
|
44
53
|
end
|
|
45
54
|
end
|
|
46
55
|
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
module Shoryuken
|
|
2
|
+
module Polling
|
|
3
|
+
QueueConfiguration = Struct.new(:name, :options) do
|
|
4
|
+
def hash
|
|
5
|
+
name.hash
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def ==(other)
|
|
9
|
+
case other
|
|
10
|
+
when String
|
|
11
|
+
if options.empty?
|
|
12
|
+
name == other
|
|
13
|
+
else
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
alias_method :eql?, :==
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
if options.empty?
|
|
25
|
+
name
|
|
26
|
+
else
|
|
27
|
+
"#<QueueConfiguration #{name} options=#{options.inspect}>"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class BaseStrategy
|
|
33
|
+
include Util
|
|
34
|
+
|
|
35
|
+
def next_queue
|
|
36
|
+
fail NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def messages_found(queue, messages_found)
|
|
40
|
+
fail NotImplementedError
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def active_queues
|
|
44
|
+
fail NotImplementedError
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def ==(other)
|
|
48
|
+
case other
|
|
49
|
+
when Array
|
|
50
|
+
@queues == other
|
|
51
|
+
else
|
|
52
|
+
if other.respond_to?(:active_queues)
|
|
53
|
+
active_queues == other.active_queues
|
|
54
|
+
else
|
|
55
|
+
false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def delay
|
|
63
|
+
Shoryuken.options[:delay].to_f
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class WeightedRoundRobin < BaseStrategy
|
|
68
|
+
def initialize(queues)
|
|
69
|
+
@initial_queues = queues
|
|
70
|
+
@queues = queues.dup.uniq
|
|
71
|
+
@paused_queues = []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def next_queue
|
|
75
|
+
unpause_queues
|
|
76
|
+
queue = @queues.shift
|
|
77
|
+
return nil if queue.nil?
|
|
78
|
+
|
|
79
|
+
@queues << queue
|
|
80
|
+
QueueConfiguration.new(queue, {})
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def messages_found(queue, messages_found)
|
|
84
|
+
if messages_found == 0
|
|
85
|
+
pause(queue)
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
maximum_weight = maximum_queue_weight(queue)
|
|
90
|
+
current_weight = current_queue_weight(queue)
|
|
91
|
+
if maximum_weight > current_weight
|
|
92
|
+
logger.info { "Increasing '#{queue}' weight to #{current_weight + 1}, max: #{maximum_weight}" }
|
|
93
|
+
@queues << queue
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def active_queues
|
|
98
|
+
unparse_queues(@queues)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def pause(queue)
|
|
104
|
+
return unless @queues.delete(queue)
|
|
105
|
+
@paused_queues << [Time.now + delay, queue]
|
|
106
|
+
logger.debug "Paused '#{queue}'"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def unpause_queues
|
|
110
|
+
return if @paused_queues.empty?
|
|
111
|
+
return if Time.now < @paused_queues.first[0]
|
|
112
|
+
pause = @paused_queues.shift
|
|
113
|
+
@queues << pause[1]
|
|
114
|
+
logger.debug "Unpaused '#{pause[1]}'"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def current_queue_weight(queue)
|
|
118
|
+
queue_weight(@queues, queue)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def maximum_queue_weight(queue)
|
|
122
|
+
queue_weight(@initial_queues, queue)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def queue_weight(queues, queue)
|
|
126
|
+
queues.count { |q| q == queue }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class StrictPriority < BaseStrategy
|
|
131
|
+
def initialize(queues)
|
|
132
|
+
# Priority ordering of the queues, highest priority first
|
|
133
|
+
@queues = queues
|
|
134
|
+
.group_by { |q| q }
|
|
135
|
+
.sort_by { |_, qs| -qs.count }
|
|
136
|
+
.map(&:first)
|
|
137
|
+
|
|
138
|
+
# Pause status of the queues, default to past time (unpaused)
|
|
139
|
+
@paused_until = queues
|
|
140
|
+
.each_with_object(Hash.new) { |queue, h| h[queue] = Time.at(0) }
|
|
141
|
+
|
|
142
|
+
# Start queues at 0
|
|
143
|
+
reset_next_queue
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def next_queue
|
|
147
|
+
next_queue = next_active_queue
|
|
148
|
+
next_queue.nil? ? nil : QueueConfiguration.new(next_queue, {})
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def messages_found(queue, messages_found)
|
|
152
|
+
if messages_found == 0
|
|
153
|
+
pause(queue)
|
|
154
|
+
else
|
|
155
|
+
reset_next_queue
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def active_queues
|
|
160
|
+
@queues
|
|
161
|
+
.reverse
|
|
162
|
+
.map.with_index(1)
|
|
163
|
+
.reject { |q, _| queue_paused?(q) }
|
|
164
|
+
.reverse
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def next_active_queue
|
|
170
|
+
reset_next_queue if queues_unpaused_since?
|
|
171
|
+
|
|
172
|
+
size = @queues.length
|
|
173
|
+
size.times do
|
|
174
|
+
queue = @queues[@next_queue_index]
|
|
175
|
+
@next_queue_index = (@next_queue_index + 1) % size
|
|
176
|
+
return queue unless queue_paused?(queue)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def queues_unpaused_since?
|
|
183
|
+
last = @last_unpause_check
|
|
184
|
+
now = @last_unpause_check = Time.now
|
|
185
|
+
|
|
186
|
+
last && @paused_until.values.any? { |t| t > last && t <= now }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def reset_next_queue
|
|
190
|
+
@next_queue_index = 0
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def queue_paused?(queue)
|
|
194
|
+
@paused_until[queue] > Time.now
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def pause(queue)
|
|
198
|
+
return unless delay > 0
|
|
199
|
+
@paused_until[queue] = Time.now + delay
|
|
200
|
+
logger.debug "Paused '#{queue}'"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
data/lib/shoryuken/processor.rb
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
require 'json'
|
|
2
|
-
|
|
3
1
|
module Shoryuken
|
|
4
2
|
class Processor
|
|
5
|
-
include Celluloid
|
|
6
3
|
include Util
|
|
7
4
|
|
|
8
5
|
def initialize(manager)
|
|
9
6
|
@manager = manager
|
|
10
7
|
end
|
|
11
8
|
|
|
12
|
-
attr_accessor :proxy_id
|
|
13
|
-
|
|
14
9
|
def process(queue, sqs_msg)
|
|
15
|
-
@manager.async.real_thread(proxy_id, Thread.current)
|
|
16
|
-
|
|
17
10
|
worker = Shoryuken.worker_registry.fetch_worker(queue, sqs_msg)
|
|
11
|
+
body = get_body(worker.class, sqs_msg)
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@manager.async.processor_done(queue, current_actor)
|
|
13
|
+
worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
|
|
14
|
+
worker.perform(sqs_msg, body)
|
|
15
|
+
end
|
|
16
|
+
ensure
|
|
17
|
+
@manager.processor_done(queue)
|
|
26
18
|
end
|
|
27
19
|
|
|
28
20
|
private
|
data/lib/shoryuken/queue.rb
CHANGED
|
@@ -35,9 +35,7 @@ module Shoryuken
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def receive_messages(options)
|
|
38
|
-
client.receive_message(options.merge(queue_url: url)).
|
|
39
|
-
messages.
|
|
40
|
-
map { |m| Message.new(client, self, m) }
|
|
38
|
+
client.receive_message(options.merge(queue_url: url)).messages.map { |m| Message.new(client, self, m) }
|
|
41
39
|
end
|
|
42
40
|
|
|
43
41
|
def fifo?
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
$stdout.sync = true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
require 'erb'
|
|
6
|
+
|
|
7
|
+
require 'shoryuken'
|
|
8
|
+
|
|
9
|
+
module Shoryuken
|
|
10
|
+
# rubocop:disable Lint/InheritException
|
|
11
|
+
# rubocop:disable Metrics/AbcSize
|
|
12
|
+
# See: https://github.com/mperham/sidekiq/blob/33f5d6b2b6c0dfaab11e5d39688cab7ebadc83ae/lib/sidekiq/cli.rb#L20
|
|
13
|
+
class Shutdown < Interrupt; end
|
|
14
|
+
|
|
15
|
+
class Runner
|
|
16
|
+
include Util
|
|
17
|
+
include Singleton
|
|
18
|
+
|
|
19
|
+
def run(options)
|
|
20
|
+
self_read, self_write = IO.pipe
|
|
21
|
+
|
|
22
|
+
%w(INT TERM USR1 USR2 TTIN).each do |sig|
|
|
23
|
+
begin
|
|
24
|
+
trap sig do
|
|
25
|
+
self_write.puts(sig)
|
|
26
|
+
end
|
|
27
|
+
rescue ArgumentError
|
|
28
|
+
puts "Signal #{sig} not supported"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
loader = EnvironmentLoader.setup_options(options)
|
|
33
|
+
|
|
34
|
+
# When cli args exist, override options in config file
|
|
35
|
+
Shoryuken.options.merge!(options)
|
|
36
|
+
|
|
37
|
+
daemonize(Shoryuken.options)
|
|
38
|
+
write_pid(Shoryuken.options)
|
|
39
|
+
|
|
40
|
+
loader.load
|
|
41
|
+
|
|
42
|
+
initialize_concurrent_logger
|
|
43
|
+
|
|
44
|
+
@launcher = Shoryuken::Launcher.new
|
|
45
|
+
|
|
46
|
+
if (callback = Shoryuken.start_callback)
|
|
47
|
+
logger.info { 'Calling Shoryuken.on_start block' }
|
|
48
|
+
callback.call
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
fire_event(:startup)
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
@launcher.run
|
|
55
|
+
|
|
56
|
+
while (readable_io = IO.select([self_read]))
|
|
57
|
+
signal = readable_io.first[0].gets.strip
|
|
58
|
+
handle_signal(signal)
|
|
59
|
+
end
|
|
60
|
+
rescue Interrupt
|
|
61
|
+
@launcher.stop(shutdown: true)
|
|
62
|
+
exit 0
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def initialize_concurrent_logger
|
|
69
|
+
return unless Shoryuken.logger
|
|
70
|
+
|
|
71
|
+
Concurrent.global_logger = lambda do |level, progname, msg = nil, &block|
|
|
72
|
+
Shoryuken.logger.log(level, msg, progname, &block)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def daemonize(options)
|
|
77
|
+
return unless options[:daemon]
|
|
78
|
+
|
|
79
|
+
files_to_reopen = []
|
|
80
|
+
ObjectSpace.each_object(File) do |file|
|
|
81
|
+
files_to_reopen << file unless file.closed?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Process.daemon(true, true)
|
|
85
|
+
|
|
86
|
+
files_to_reopen.each do |file|
|
|
87
|
+
begin
|
|
88
|
+
file.reopen file.path, 'a+'
|
|
89
|
+
file.sync = true
|
|
90
|
+
rescue ::Exception
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
[$stdout, $stderr].each do |io|
|
|
95
|
+
File.open(options[:logfile], 'ab') do |f|
|
|
96
|
+
io.reopen(f)
|
|
97
|
+
end
|
|
98
|
+
io.sync = true
|
|
99
|
+
end
|
|
100
|
+
$stdin.reopen('/dev/null')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def write_pid(options)
|
|
104
|
+
return unless (path = options[:pidfile])
|
|
105
|
+
|
|
106
|
+
File.open(path, 'w') { |f| f.puts(Process.pid) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def execute_soft_shutdown
|
|
110
|
+
logger.info { 'Received USR1, will soft shutdown down' }
|
|
111
|
+
|
|
112
|
+
@launcher.stop
|
|
113
|
+
fire_event(:quiet, true)
|
|
114
|
+
exit 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def print_threads_backtrace
|
|
118
|
+
Thread.list.each do |thread|
|
|
119
|
+
logger.info { "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" }
|
|
120
|
+
if thread.backtrace
|
|
121
|
+
logger.info { thread.backtrace.join("\n") }
|
|
122
|
+
else
|
|
123
|
+
logger.info { '<no backtrace available>' }
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def handle_signal(sig)
|
|
129
|
+
logger.info { "Got #{sig} signal" }
|
|
130
|
+
|
|
131
|
+
case sig
|
|
132
|
+
when 'USR1' then execute_soft_shutdown
|
|
133
|
+
when 'TTIN' then print_threads_backtrace
|
|
134
|
+
when 'USR2'
|
|
135
|
+
logger.warn { "Received #{sig}, will do nothing. To execute soft shutdown, please send USR1" }
|
|
136
|
+
else
|
|
137
|
+
logger.info { "Received #{sig}, will shutdown down" }
|
|
138
|
+
|
|
139
|
+
raise Interrupt
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
data/lib/shoryuken/util.rb
CHANGED
data/lib/shoryuken/version.rb
CHANGED