puma 4.3.12 → 5.6.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1511 -524
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +44 -10
  28. data/ext/puma_http11/http11_parser.c +45 -47
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +0 -0
  33. data/ext/puma_http11/mini_ssl.c +225 -89
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +50 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +104 -76
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +13 -6
  49. data/lib/puma/control_cli.rb +99 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +368 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +128 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +137 -50
  60. data/lib/puma/null_io.rb +18 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +476 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +292 -763
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +48 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +32 -4
  76. data/lib/puma.rb +48 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/lib/rack/version_restriction.rb +15 -0
  79. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  80. data/tools/trickletest.rb +0 -0
  81. metadata +28 -23
  82. data/docs/tcp_mode.md +0 -96
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  85. data/lib/puma/accept_nonblock.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -41
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/client.rb CHANGED
@@ -72,6 +72,7 @@ module Puma
72
72
  @parser = HttpParser.new
73
73
  @parsed_bytes = 0
74
74
  @read_header = true
75
+ @read_proxy = false
75
76
  @ready = false
76
77
 
77
78
  @body = nil
@@ -85,7 +86,9 @@ module Puma
85
86
  @hijacked = false
86
87
 
87
88
  @peerip = nil
89
+ @listener = nil
88
90
  @remote_addr_header = nil
91
+ @expect_proxy_proto = false
89
92
 
90
93
  @body_remain = 0
91
94
 
@@ -97,10 +100,17 @@ module Puma
97
100
 
98
101
  attr_writer :peerip
99
102
 
100
- attr_accessor :remote_addr_header
103
+ attr_accessor :remote_addr_header, :listener
101
104
 
102
105
  def_delegators :@io, :closed?
103
106
 
107
+ # Test to see if io meets a bare minimum of functioning, @to_io needs to be
108
+ # used for MiniSSL::Socket
109
+ def io_ok?
110
+ @to_io.is_a?(::BasicSocket) && !closed?
111
+ end
112
+
113
+ # @!attribute [r] inspect
104
114
  def inspect
105
115
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
106
116
  end
@@ -112,27 +122,36 @@ module Puma
112
122
  env[HIJACK_IO] ||= @io
113
123
  end
114
124
 
125
+ # @!attribute [r] in_data_phase
115
126
  def in_data_phase
116
- !@read_header
127
+ !(@read_header || @read_proxy)
117
128
  end
118
129
 
119
130
  def set_timeout(val)
120
- @timeout_at = Time.now + val
131
+ @timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
132
+ end
133
+
134
+ # Number of seconds until the timeout elapses.
135
+ def timeout
136
+ [@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
121
137
  end
122
138
 
123
139
  def reset(fast_check=true)
124
140
  @parser.reset
125
141
  @read_header = true
142
+ @read_proxy = !!@expect_proxy_proto
126
143
  @env = @proto_env.dup
127
144
  @body = nil
128
145
  @tempfile = nil
129
146
  @parsed_bytes = 0
130
147
  @ready = false
131
148
  @body_remain = 0
132
- @peerip = nil
149
+ @peerip = nil if @remote_addr_header
133
150
  @in_last_chunk = false
134
151
 
135
152
  if @buffer
153
+ return false unless try_to_parse_proxy_protocol
154
+
136
155
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
137
156
 
138
157
  if @parser.finished?
@@ -145,8 +164,7 @@ module Puma
145
164
  return false
146
165
  else
147
166
  begin
148
- if fast_check &&
149
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
167
+ if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
150
168
  return try_to_finish
151
169
  end
152
170
  rescue IOError
@@ -159,19 +177,45 @@ module Puma
159
177
  def close
160
178
  begin
161
179
  @io.close
162
- rescue IOError
163
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
180
+ rescue IOError, Errno::EBADF
181
+ Puma::Util.purge_interrupt_queue
182
+ end
183
+ end
184
+
185
+ # If necessary, read the PROXY protocol from the buffer. Returns
186
+ # false if more data is needed.
187
+ def try_to_parse_proxy_protocol
188
+ if @read_proxy
189
+ if @expect_proxy_proto == :v1
190
+ if @buffer.include? "\r\n"
191
+ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
192
+ if md[1]
193
+ @peerip = md[1].split(" ")[0]
194
+ end
195
+ @buffer = md.post_match
196
+ end
197
+ # if the buffer has a \r\n but doesn't have a PROXY protocol
198
+ # request, this is just HTTP from a non-PROXY client; move on
199
+ @read_proxy = false
200
+ return @buffer.size > 0
201
+ else
202
+ return false
203
+ end
204
+ end
164
205
  end
206
+ true
165
207
  end
166
208
 
167
209
  def try_to_finish
168
- return read_body unless @read_header
210
+ return read_body if in_data_phase
169
211
 
170
212
  begin
171
213
  data = @io.read_nonblock(CHUNK_SIZE)
172
214
  rescue IO::WaitReadable
173
215
  return false
174
- rescue SystemCallError, IOError, EOFError
216
+ rescue EOFError
217
+ # Swallow error, don't log
218
+ rescue SystemCallError, IOError
175
219
  raise ConnectionError, "Connection error detected during read"
176
220
  end
177
221
 
@@ -188,6 +232,8 @@ module Puma
188
232
  @buffer = data
189
233
  end
190
234
 
235
+ return false unless try_to_parse_proxy_protocol
236
+
191
237
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
192
238
 
193
239
  if @parser.finished?
@@ -200,68 +246,20 @@ module Puma
200
246
  false
201
247
  end
202
248
 
203
- if IS_JRUBY
204
- def jruby_start_try_to_finish
205
- return read_body unless @read_header
206
-
207
- begin
208
- data = @io.sysread_nonblock(CHUNK_SIZE)
209
- rescue OpenSSL::SSL::SSLError => e
210
- return false if e.kind_of? IO::WaitReadable
211
- raise e
212
- end
213
-
214
- # No data means a closed socket
215
- unless data
216
- @buffer = nil
217
- set_ready
218
- raise EOFError
219
- end
220
-
221
- if @buffer
222
- @buffer << data
223
- else
224
- @buffer = data
225
- end
226
-
227
- @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
228
-
229
- if @parser.finished?
230
- return setup_body
231
- elsif @parsed_bytes >= MAX_HEADER
232
- raise HttpParserError,
233
- "HEADER is longer than allowed, aborting client early."
234
- end
235
-
236
- false
237
- end
238
-
239
- def eagerly_finish
240
- return true if @ready
241
-
242
- if @io.kind_of? OpenSSL::SSL::SSLSocket
243
- return true if jruby_start_try_to_finish
244
- end
245
-
246
- return false unless IO.select([@to_io], nil, nil, 0)
247
- try_to_finish
248
- end
249
-
250
- else
249
+ def eagerly_finish
250
+ return true if @ready
251
+ return false unless @to_io.wait_readable(0)
252
+ try_to_finish
253
+ end
251
254
 
252
- def eagerly_finish
253
- return true if @ready
254
- return false unless IO.select([@to_io], nil, nil, 0)
255
- try_to_finish
256
- end
257
- end # IS_JRUBY
255
+ def finish(timeout)
256
+ return if @ready
257
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
258
+ end
258
259
 
259
- def finish
260
- return true if @ready
261
- until try_to_finish
262
- IO.select([@to_io], nil, nil)
263
- end
264
- true
260
+ def timeout!
261
+ write_error(408) if in_data_phase
262
+ raise ConnectionError
265
263
  end
266
264
 
267
265
  def write_error(status_code)
@@ -275,7 +273,7 @@ module Puma
275
273
  return @peerip if @peerip
276
274
 
277
275
  if @remote_addr_header
278
- hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
276
+ hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
279
277
  @peerip = hdr
280
278
  return hdr
281
279
  end
@@ -283,6 +281,26 @@ module Puma
283
281
  @peerip ||= @io.peeraddr.last
284
282
  end
285
283
 
284
+ # Returns true if the persistent connection can be closed immediately
285
+ # without waiting for the configured idle/shutdown timeout.
286
+ # @version 5.0.0
287
+ #
288
+ def can_close?
289
+ # Allow connection to close if we're not in the middle of parsing a request.
290
+ @parsed_bytes == 0
291
+ end
292
+
293
+ def expect_proxy_proto=(val)
294
+ if val
295
+ if @read_header
296
+ @read_proxy = true
297
+ end
298
+ else
299
+ @read_proxy = false
300
+ end
301
+ @expect_proxy_proto = val
302
+ end
303
+
286
304
  private
287
305
 
288
306
  def setup_body
@@ -351,6 +369,7 @@ module Puma
351
369
 
352
370
  if remain > MAX_BODY
353
371
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
372
+ @body.unlink
354
373
  @body.binmode
355
374
  @tempfile = @body
356
375
  else
@@ -363,7 +382,7 @@ module Puma
363
382
 
364
383
  @body_remain = remain
365
384
 
366
- return false
385
+ false
367
386
  end
368
387
 
369
388
  def read_body
@@ -430,7 +449,7 @@ module Puma
430
449
  end
431
450
 
432
451
  if decode_chunk(chunk)
433
- @env[CONTENT_LENGTH] = @chunked_content_length
452
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
434
453
  return true
435
454
  end
436
455
  end
@@ -442,17 +461,18 @@ module Puma
442
461
  @prev_chunk = ""
443
462
 
444
463
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
464
+ @body.unlink
445
465
  @body.binmode
446
466
  @tempfile = @body
447
-
448
467
  @chunked_content_length = 0
449
468
 
450
469
  if decode_chunk(body)
451
- @env[CONTENT_LENGTH] = @chunked_content_length
470
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
452
471
  return true
453
472
  end
454
473
  end
455
474
 
475
+ # @version 5.0.0
456
476
  def write_chunk(str)
457
477
  @chunked_content_length += @body.write(str)
458
478
  end
@@ -466,7 +486,15 @@ module Puma
466
486
  chunk = chunk[@partial_part_left..-1]
467
487
  @partial_part_left = 0
468
488
  else
469
- write_chunk(chunk) if @partial_part_left > 2 # don't include the last \r\n
489
+ if @partial_part_left > 2
490
+ if @partial_part_left == chunk.size + 1
491
+ # Don't include the last \r
492
+ write_chunk(chunk[0..(@partial_part_left-3)])
493
+ else
494
+ # don't include the last \r\n
495
+ write_chunk(chunk)
496
+ end
497
+ end
470
498
  @partial_part_left -= chunk.size
471
499
  return false
472
500
  end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ class Cluster < Puma::Runner
5
+ # This class is instantiated by the `Puma::Cluster` and represents a single
6
+ # worker process.
7
+ #
8
+ # At the core of this class is running an instance of `Puma::Server` which
9
+ # gets created via the `start_server` method from the `Puma::Runner` class
10
+ # that this inherits from.
11
+ class Worker < Puma::Runner
12
+ attr_reader :index, :master
13
+
14
+ def initialize(index:, master:, launcher:, pipes:, server: nil)
15
+ super launcher, launcher.events
16
+
17
+ @index = index
18
+ @master = master
19
+ @launcher = launcher
20
+ @options = launcher.options
21
+ @check_pipe = pipes[:check_pipe]
22
+ @worker_write = pipes[:worker_write]
23
+ @fork_pipe = pipes[:fork_pipe]
24
+ @wakeup = pipes[:wakeup]
25
+ @server = server
26
+ end
27
+
28
+ def run
29
+ title = "puma: cluster worker #{index}: #{master}"
30
+ title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
31
+ $0 = title
32
+
33
+ Signal.trap "SIGINT", "IGNORE"
34
+ Signal.trap "SIGCHLD", "DEFAULT"
35
+
36
+ Thread.new do
37
+ Puma.set_thread_name "wrkr check"
38
+ @check_pipe.wait_readable
39
+ log "! Detected parent died, dying"
40
+ exit! 1
41
+ end
42
+
43
+ # If we're not running under a Bundler context, then
44
+ # report the info about the context we will be using
45
+ if !ENV['BUNDLE_GEMFILE']
46
+ if File.exist?("Gemfile")
47
+ log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
48
+ elsif File.exist?("gems.rb")
49
+ log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
50
+ end
51
+ end
52
+
53
+ # Invoke any worker boot hooks so they can get
54
+ # things in shape before booting the app.
55
+ @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
56
+
57
+ begin
58
+ server = @server ||= start_server
59
+ rescue Exception => e
60
+ log "! Unable to start worker"
61
+ log e.backtrace[0]
62
+ exit 1
63
+ end
64
+
65
+ restart_server = Queue.new << true << false
66
+
67
+ fork_worker = @options[:fork_worker] && index == 0
68
+
69
+ if fork_worker
70
+ restart_server.clear
71
+ worker_pids = []
72
+ Signal.trap "SIGCHLD" do
73
+ wakeup! if worker_pids.reject! do |p|
74
+ Process.wait(p, Process::WNOHANG) rescue true
75
+ end
76
+ end
77
+
78
+ Thread.new do
79
+ Puma.set_thread_name "wrkr fork"
80
+ while (idx = @fork_pipe.gets)
81
+ idx = idx.to_i
82
+ if idx == -1 # stop server
83
+ if restart_server.length > 0
84
+ restart_server.clear
85
+ server.begin_restart(true)
86
+ @launcher.config.run_hooks :before_refork, nil, @launcher.events
87
+ Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
88
+ end
89
+ elsif idx == 0 # restart server
90
+ restart_server << true << false
91
+ else # fork worker
92
+ worker_pids << pid = spawn_worker(idx)
93
+ @worker_write << "f#{pid}:#{idx}\n" rescue nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ Signal.trap "SIGTERM" do
100
+ @worker_write << "e#{Process.pid}\n" rescue nil
101
+ restart_server.clear
102
+ server.stop
103
+ restart_server << false
104
+ end
105
+
106
+ begin
107
+ @worker_write << "b#{Process.pid}:#{index}\n"
108
+ rescue SystemCallError, IOError
109
+ Puma::Util.purge_interrupt_queue
110
+ STDERR.puts "Master seems to have exited, exiting."
111
+ return
112
+ end
113
+
114
+ while restart_server.pop
115
+ server_thread = server.run
116
+ stat_thread ||= Thread.new(@worker_write) do |io|
117
+ Puma.set_thread_name "stat pld"
118
+ base_payload = "p#{Process.pid}"
119
+
120
+ while true
121
+ begin
122
+ b = server.backlog || 0
123
+ r = server.running || 0
124
+ t = server.pool_capacity || 0
125
+ m = server.max_threads || 0
126
+ rc = server.requests_count || 0
127
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
128
+ io << payload
129
+ rescue IOError
130
+ Puma::Util.purge_interrupt_queue
131
+ break
132
+ end
133
+ sleep @options[:worker_check_interval]
134
+ end
135
+ end
136
+ server_thread.join
137
+ end
138
+
139
+ # Invoke any worker shutdown hooks so they can prevent the worker
140
+ # exiting until any background operations are completed
141
+ @launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
142
+ ensure
143
+ @worker_write << "t#{Process.pid}\n" rescue nil
144
+ @worker_write.close
145
+ end
146
+
147
+ private
148
+
149
+ def spawn_worker(idx)
150
+ @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
151
+
152
+ pid = fork do
153
+ new_worker = Worker.new index: idx,
154
+ master: master,
155
+ launcher: @launcher,
156
+ pipes: { check_pipe: @check_pipe,
157
+ worker_write: @worker_write },
158
+ server: @server
159
+ new_worker.run
160
+ end
161
+
162
+ if !pid
163
+ log "! Complete inability to spawn new workers detected"
164
+ log "! Seppuku is the only choice."
165
+ exit! 1
166
+ end
167
+
168
+ @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
169
+ pid
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ class Cluster < Runner
5
+ # This class represents a worker process from the perspective of the puma
6
+ # master process. It contains information about the process and its health
7
+ # and it exposes methods to control the process via IPC. It does not
8
+ # include the actual logic executed by the worker process itself. For that,
9
+ # see Puma::Cluster::Worker.
10
+ class WorkerHandle
11
+ def initialize(idx, pid, phase, options)
12
+ @index = idx
13
+ @pid = pid
14
+ @phase = phase
15
+ @stage = :started
16
+ @signal = "TERM"
17
+ @options = options
18
+ @first_term_sent = nil
19
+ @started_at = Time.now
20
+ @last_checkin = Time.now
21
+ @last_status = {}
22
+ @term = false
23
+ end
24
+
25
+ attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
26
+
27
+ # @version 5.0.0
28
+ attr_writer :pid, :phase
29
+
30
+ def booted?
31
+ @stage == :booted
32
+ end
33
+
34
+ def uptime
35
+ Time.now - started_at
36
+ end
37
+
38
+ def boot!
39
+ @last_checkin = Time.now
40
+ @stage = :booted
41
+ end
42
+
43
+ def term!
44
+ @term = true
45
+ end
46
+
47
+ def term?
48
+ @term
49
+ end
50
+
51
+ def ping!(status)
52
+ @last_checkin = Time.now
53
+ captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
54
+ @last_status = captures.names.inject({}) do |hash, key|
55
+ hash[key.to_sym] = captures[key].to_i
56
+ hash
57
+ end
58
+ end
59
+
60
+ # @see Puma::Cluster#check_workers
61
+ # @version 5.0.0
62
+ def ping_timeout
63
+ @last_checkin +
64
+ (booted? ?
65
+ @options[:worker_timeout] :
66
+ @options[:worker_boot_timeout]
67
+ )
68
+ end
69
+
70
+ def term
71
+ begin
72
+ if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
73
+ @signal = "KILL"
74
+ else
75
+ @term ||= true
76
+ @first_term_sent ||= Time.now
77
+ end
78
+ Process.kill @signal, @pid if @pid
79
+ rescue Errno::ESRCH
80
+ end
81
+ end
82
+
83
+ def kill
84
+ @signal = 'KILL'
85
+ term
86
+ end
87
+
88
+ def hup
89
+ Process.kill "HUP", @pid
90
+ rescue Errno::ESRCH
91
+ end
92
+ end
93
+ end
94
+ end