puma 3.9.0 → 3.12.0
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.
- checksums.yaml +5 -5
- data/History.md +98 -0
- data/README.md +140 -230
- data/docs/architecture.md +36 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +0 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/plugins.md +28 -0
- data/docs/restart.md +39 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +112 -37
- data/ext/puma_http11/http11_parser.c +84 -84
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/mini_ssl.c +18 -4
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +6 -0
- data/lib/puma.rb +8 -0
- data/lib/puma/app/status.rb +8 -0
- data/lib/puma/binder.rb +12 -8
- data/lib/puma/cli.rb +20 -7
- data/lib/puma/client.rb +28 -0
- data/lib/puma/cluster.rb +26 -7
- data/lib/puma/configuration.rb +19 -14
- data/lib/puma/const.rb +7 -2
- data/lib/puma/control_cli.rb +5 -5
- data/lib/puma/dsl.rb +34 -7
- data/lib/puma/jruby_restart.rb +0 -1
- data/lib/puma/launcher.rb +36 -19
- data/lib/puma/minissl.rb +49 -27
- data/lib/puma/plugin/tmp_restart.rb +0 -1
- data/lib/puma/reactor.rb +135 -0
- data/lib/puma/runner.rb +12 -1
- data/lib/puma/server.rb +84 -25
- data/lib/puma/single.rb +12 -3
- data/lib/puma/thread_pool.rb +47 -8
- data/lib/rack/handler/puma.rb +4 -1
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +2 -2
- data/tools/jungle/init.d/run-puma +1 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/trickletest.rb +1 -1
- metadata +21 -94
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -12
- data/Manifest.txt +0 -78
- data/Rakefile +0 -158
- data/Release.md +0 -9
- data/gemfiles/2.1-Gemfile +0 -12
- data/puma.gemspec +0 -52
data/lib/puma/cluster.rb
CHANGED
@@ -5,6 +5,17 @@ require 'puma/plugin'
|
|
5
5
|
require 'time'
|
6
6
|
|
7
7
|
module Puma
|
8
|
+
# This class is instantiated by the `Puma::Launcher` and used
|
9
|
+
# to boot and serve a Ruby application when puma "workers" are needed
|
10
|
+
# i.e. when using multi-processes. For example `$ puma -w 5`
|
11
|
+
#
|
12
|
+
# At the core of this class is running an instance of `Puma::Server` which
|
13
|
+
# gets created via the `start_server` method from the `Puma::Runner` class
|
14
|
+
# that this inherits from.
|
15
|
+
#
|
16
|
+
# An instance of this class will spawn the number of processes passed in
|
17
|
+
# via the `spawn_workers` method call. Each worker will have it's own
|
18
|
+
# instance of a `Puma::Server`.
|
8
19
|
class Cluster < Runner
|
9
20
|
WORKER_CHECK_INTERVAL = 5
|
10
21
|
|
@@ -24,7 +35,7 @@ module Puma
|
|
24
35
|
@workers.each { |x| x.term }
|
25
36
|
|
26
37
|
begin
|
27
|
-
Process.
|
38
|
+
@workers.each { |w| Process.waitpid(w.pid) }
|
28
39
|
rescue Interrupt
|
29
40
|
log "! Cancelled waiting for workers"
|
30
41
|
end
|
@@ -224,12 +235,13 @@ module Puma
|
|
224
235
|
begin
|
225
236
|
@wakeup.write "!" unless @wakeup.closed?
|
226
237
|
rescue SystemCallError, IOError
|
238
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
227
239
|
end
|
228
240
|
end
|
229
241
|
|
230
242
|
def worker(index, master)
|
231
|
-
title
|
232
|
-
title
|
243
|
+
title = "puma: cluster worker #{index}: #{master}"
|
244
|
+
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
233
245
|
$0 = title
|
234
246
|
|
235
247
|
Signal.trap "SIGINT", "IGNORE"
|
@@ -267,6 +279,7 @@ module Puma
|
|
267
279
|
begin
|
268
280
|
@worker_write << "b#{Process.pid}\n"
|
269
281
|
rescue SystemCallError, IOError
|
282
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
270
283
|
STDERR.puts "Master seems to have exited, exiting."
|
271
284
|
return
|
272
285
|
end
|
@@ -277,11 +290,14 @@ module Puma
|
|
277
290
|
while true
|
278
291
|
sleep WORKER_CHECK_INTERVAL
|
279
292
|
begin
|
280
|
-
b = server.backlog
|
281
|
-
r = server.running
|
282
|
-
|
293
|
+
b = server.backlog || 0
|
294
|
+
r = server.running || 0
|
295
|
+
t = server.pool_capacity || 0
|
296
|
+
m = server.max_threads || 0
|
297
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m} }\n!
|
283
298
|
io << payload
|
284
299
|
rescue IOError
|
300
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
285
301
|
break
|
286
302
|
end
|
287
303
|
end
|
@@ -337,7 +353,7 @@ module Puma
|
|
337
353
|
def stats
|
338
354
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
339
355
|
booted_worker_count = @workers.count { |w| w.booted? }
|
340
|
-
worker_status = '[' + @workers.map{ |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
|
356
|
+
worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
|
341
357
|
%Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
|
342
358
|
end
|
343
359
|
|
@@ -372,7 +388,10 @@ module Puma
|
|
372
388
|
log "Early termination of worker"
|
373
389
|
exit! 0
|
374
390
|
else
|
391
|
+
stop_workers
|
375
392
|
stop
|
393
|
+
|
394
|
+
raise SignalException, "SIGTERM"
|
376
395
|
end
|
377
396
|
end
|
378
397
|
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -180,30 +180,31 @@ module Puma
|
|
180
180
|
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
|
181
181
|
:remote_address => :socket,
|
182
182
|
:tag => method(:infer_tag),
|
183
|
-
:environment => ->{ ENV['RACK_ENV'] || "development" },
|
183
|
+
:environment => -> { ENV['RACK_ENV'] || "development" },
|
184
184
|
:rackup => DefaultRackup,
|
185
185
|
:logger => STDOUT,
|
186
|
-
:persistent_timeout => Const::PERSISTENT_TIMEOUT
|
186
|
+
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
187
|
+
:first_data_timeout => Const::FIRST_DATA_TIMEOUT
|
187
188
|
}
|
188
189
|
end
|
189
190
|
|
190
191
|
def load
|
192
|
+
config_files.each { |config_file| @file_dsl._load_from(config_file) }
|
193
|
+
|
194
|
+
@options
|
195
|
+
end
|
196
|
+
|
197
|
+
def config_files
|
191
198
|
files = @options.all_of(:config_files)
|
192
199
|
|
193
|
-
if files
|
194
|
-
|
195
|
-
File.exist?(f)
|
196
|
-
}
|
200
|
+
return [] if files == ['-']
|
201
|
+
return files if files.any?
|
197
202
|
|
198
|
-
|
199
|
-
|
200
|
-
files = []
|
203
|
+
first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
|
204
|
+
File.exist?(f)
|
201
205
|
end
|
202
206
|
|
203
|
-
|
204
|
-
@file_dsl._load_from(f)
|
205
|
-
end
|
206
|
-
@options
|
207
|
+
[first_default_file]
|
207
208
|
end
|
208
209
|
|
209
210
|
# Call once all configuration (included from rackup files)
|
@@ -263,6 +264,10 @@ module Puma
|
|
263
264
|
@options[:environment]
|
264
265
|
end
|
265
266
|
|
267
|
+
def environment_str
|
268
|
+
environment.respond_to?(:call) ? environment.call : environment
|
269
|
+
end
|
270
|
+
|
266
271
|
def load_plugin(name)
|
267
272
|
@plugins.create name
|
268
273
|
end
|
@@ -340,7 +345,7 @@ module Puma
|
|
340
345
|
end
|
341
346
|
|
342
347
|
if bytes
|
343
|
-
token = ""
|
348
|
+
token = "".dup
|
344
349
|
bytes.each_byte { |b| token << b.to_s(16) }
|
345
350
|
else
|
346
351
|
token = (0..count).to_a.map { rand(255).to_s(16) }.join
|
data/lib/puma/const.rb
CHANGED
@@ -53,6 +53,8 @@ module Puma
|
|
53
53
|
415 => 'Unsupported Media Type',
|
54
54
|
416 => 'Range Not Satisfiable',
|
55
55
|
417 => 'Expectation Failed',
|
56
|
+
418 => 'I\'m A Teapot',
|
57
|
+
421 => 'Misdirected Request',
|
56
58
|
422 => 'Unprocessable Entity',
|
57
59
|
423 => 'Locked',
|
58
60
|
424 => 'Failed Dependency',
|
@@ -60,6 +62,7 @@ module Puma
|
|
60
62
|
428 => 'Precondition Required',
|
61
63
|
429 => 'Too Many Requests',
|
62
64
|
431 => 'Request Header Fields Too Large',
|
65
|
+
451 => 'Unavailable For Legal Reasons',
|
63
66
|
500 => 'Internal Server Error',
|
64
67
|
501 => 'Not Implemented',
|
65
68
|
502 => 'Bad Gateway',
|
@@ -95,8 +98,8 @@ module Puma
|
|
95
98
|
# too taxing on performance.
|
96
99
|
module Const
|
97
100
|
|
98
|
-
PUMA_VERSION = VERSION = "3.
|
99
|
-
CODE_NAME = "
|
101
|
+
PUMA_VERSION = VERSION = "3.12.0".freeze
|
102
|
+
CODE_NAME = "Llamas in Pajamas".freeze
|
100
103
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
101
104
|
|
102
105
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -220,5 +223,7 @@ module Puma
|
|
220
223
|
HIJACK_P = "rack.hijack?".freeze
|
221
224
|
HIJACK = "rack.hijack".freeze
|
222
225
|
HIJACK_IO = "rack.hijack_io".freeze
|
226
|
+
|
227
|
+
EARLY_HINTS = "rack.early_hints".freeze
|
223
228
|
end
|
224
229
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -9,7 +9,7 @@ require 'socket'
|
|
9
9
|
module Puma
|
10
10
|
class ControlCLI
|
11
11
|
|
12
|
-
COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory}
|
12
|
+
COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
|
13
13
|
|
14
14
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
15
15
|
@state = nil
|
@@ -69,6 +69,7 @@ module Puma
|
|
69
69
|
end
|
70
70
|
|
71
71
|
opts.order!(argv) { |a| opts.terminate a }
|
72
|
+
opts.parse!
|
72
73
|
|
73
74
|
@command = argv.shift
|
74
75
|
|
@@ -169,7 +170,7 @@ module Puma
|
|
169
170
|
end
|
170
171
|
|
171
172
|
message "Command #{@command} sent success"
|
172
|
-
message response.last if @command == "stats"
|
173
|
+
message response.last if @command == "stats" || @command == "gc-stats"
|
173
174
|
end
|
174
175
|
|
175
176
|
@server.close
|
@@ -204,7 +205,6 @@ module Puma
|
|
204
205
|
Process.kill "SIGUSR1", @pid
|
205
206
|
|
206
207
|
else
|
207
|
-
message "Puma is started"
|
208
208
|
return
|
209
209
|
end
|
210
210
|
|
@@ -220,7 +220,7 @@ module Puma
|
|
220
220
|
end
|
221
221
|
|
222
222
|
def run
|
223
|
-
start if @command == "start"
|
223
|
+
return start if @command == "start"
|
224
224
|
|
225
225
|
prepare_configuration
|
226
226
|
|
@@ -245,7 +245,7 @@ module Puma
|
|
245
245
|
run_args += ["-S", @state] if @state
|
246
246
|
run_args += ["-q"] if @quiet
|
247
247
|
run_args += ["--pidfile", @pidfile] if @pidfile
|
248
|
-
run_args += ["--control", @control_url] if @control_url
|
248
|
+
run_args += ["--control-url", @control_url] if @control_url
|
249
249
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
250
250
|
run_args += ["-C", @config_file] if @config_file
|
251
251
|
|
data/lib/puma/dsl.rb
CHANGED
@@ -110,14 +110,31 @@ module Puma
|
|
110
110
|
@options[:config_files] << file
|
111
111
|
end
|
112
112
|
|
113
|
-
#
|
114
|
-
# protocols.
|
113
|
+
# Adds a binding for the server to +url+. tcp://, unix://, and ssl:// are the only accepted
|
114
|
+
# protocols. Use query parameters within the url to specify options.
|
115
115
|
#
|
116
|
+
# @note multiple urls can be bound to, calling `bind` does not overwrite previous bindings.
|
117
|
+
#
|
118
|
+
# @example Explicitly the socket backlog depth (default is 1024)
|
119
|
+
# bind('unix:///var/run/puma.sock?backlog=2048')
|
120
|
+
#
|
121
|
+
# @example Set up ssl cert
|
122
|
+
# bind('ssl://127.0.0.1:9292?key=key.key&cert=cert.pem')
|
123
|
+
#
|
124
|
+
# @example Prefer low-latency over higher throughput (via `Socket::TCP_NODELAY`)
|
125
|
+
# bind('tcp://0.0.0.0:9292?low_latency=true')
|
126
|
+
#
|
127
|
+
# @example Set socket permissions
|
128
|
+
# bind('unix:///var/run/puma.sock?umask=0111')
|
116
129
|
def bind(url)
|
117
130
|
@options[:binds] ||= []
|
118
131
|
@options[:binds] << url
|
119
132
|
end
|
120
133
|
|
134
|
+
def clear_binds!
|
135
|
+
@options[:binds] = []
|
136
|
+
end
|
137
|
+
|
121
138
|
# Define the TCP port to bind to. Use +bind+ for more advanced options.
|
122
139
|
#
|
123
140
|
def port(port, host=nil)
|
@@ -129,7 +146,13 @@ module Puma
|
|
129
146
|
# them
|
130
147
|
#
|
131
148
|
def persistent_timeout(seconds)
|
132
|
-
@options[:persistent_timeout] = seconds
|
149
|
+
@options[:persistent_timeout] = Integer(seconds)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Define how long the tcp socket stays open, if no data has been received
|
153
|
+
#
|
154
|
+
def first_data_timeout(seconds)
|
155
|
+
@options[:first_data_timeout] = Integer(seconds)
|
133
156
|
end
|
134
157
|
|
135
158
|
# Work around leaky apps that leave garbage in Thread locals
|
@@ -146,7 +169,7 @@ module Puma
|
|
146
169
|
end
|
147
170
|
|
148
171
|
# When shutting down, drain the accept socket of pending
|
149
|
-
# connections and
|
172
|
+
# connections and process them. This loops over the accept
|
150
173
|
# socket until there are no more read events and then stops
|
151
174
|
# looking and waits for the requests to finish.
|
152
175
|
def drain_on_shutdown(which=true)
|
@@ -231,6 +254,10 @@ module Puma
|
|
231
254
|
@options[:mode] = :tcp
|
232
255
|
end
|
233
256
|
|
257
|
+
def early_hints(answer=true)
|
258
|
+
@options[:early_hints] = answer
|
259
|
+
end
|
260
|
+
|
234
261
|
# Redirect STDOUT and STDERR to files specified.
|
235
262
|
def stdout_redirect(stdout=nil, stderr=nil, append=false)
|
236
263
|
@options[:redirect_stdout] = stdout
|
@@ -397,17 +424,17 @@ module Puma
|
|
397
424
|
# that have not checked in within the given +timeout+.
|
398
425
|
# This mitigates hung processes. Default value is 60 seconds.
|
399
426
|
def worker_timeout(timeout)
|
400
|
-
@options[:worker_timeout] = timeout
|
427
|
+
@options[:worker_timeout] = Integer(timeout)
|
401
428
|
end
|
402
429
|
|
403
430
|
# *Cluster mode only* Set the timeout for workers to boot
|
404
431
|
def worker_boot_timeout(timeout)
|
405
|
-
@options[:worker_boot_timeout] = timeout
|
432
|
+
@options[:worker_boot_timeout] = Integer(timeout)
|
406
433
|
end
|
407
434
|
|
408
435
|
# *Cluster mode only* Set the timeout for worker shutdown
|
409
436
|
def worker_shutdown_timeout(timeout)
|
410
|
-
@options[:worker_shutdown_timeout] = timeout
|
437
|
+
@options[:worker_shutdown_timeout] = Integer(timeout)
|
411
438
|
end
|
412
439
|
|
413
440
|
# When set to true (the default), workers accept all requests
|
data/lib/puma/jruby_restart.rb
CHANGED
data/lib/puma/launcher.rb
CHANGED
@@ -40,7 +40,7 @@ module Puma
|
|
40
40
|
# [200, {}, ["hello world"]]
|
41
41
|
# end
|
42
42
|
# end
|
43
|
-
# Puma::Launcher.new(conf,
|
43
|
+
# Puma::Launcher.new(conf, events: Puma::Events.stdio).run
|
44
44
|
def initialize(conf, launcher_args={})
|
45
45
|
@runner = nil
|
46
46
|
@events = launcher_args[:events] || Events::DEFAULT
|
@@ -86,6 +86,7 @@ module Puma
|
|
86
86
|
else
|
87
87
|
@runner = Single.new(self, @events)
|
88
88
|
end
|
89
|
+
Puma.stats_object = @runner
|
89
90
|
|
90
91
|
@status = :run
|
91
92
|
end
|
@@ -163,7 +164,16 @@ module Puma
|
|
163
164
|
|
164
165
|
# Run the server. This blocks until the server is stopped
|
165
166
|
def run
|
166
|
-
previous_env =
|
167
|
+
previous_env =
|
168
|
+
if defined?(Bundler)
|
169
|
+
env = Bundler::ORIGINAL_ENV.dup
|
170
|
+
# add -rbundler/setup so we load from Gemfile when restarting
|
171
|
+
bundle = "-rbundler/setup"
|
172
|
+
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
173
|
+
env
|
174
|
+
else
|
175
|
+
ENV.to_h
|
176
|
+
end
|
167
177
|
|
168
178
|
@config.clamp
|
169
179
|
|
@@ -225,8 +235,8 @@ module Puma
|
|
225
235
|
else
|
226
236
|
redirects = {:close_others => true}
|
227
237
|
@binder.listeners.each_with_index do |(l, io), i|
|
228
|
-
|
229
|
-
|
238
|
+
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
239
|
+
redirects[io.to_i] = io.to_i
|
230
240
|
end
|
231
241
|
|
232
242
|
argv = restart_args
|
@@ -289,8 +299,8 @@ module Puma
|
|
289
299
|
end
|
290
300
|
|
291
301
|
def title
|
292
|
-
buffer
|
293
|
-
buffer
|
302
|
+
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
303
|
+
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
294
304
|
buffer
|
295
305
|
end
|
296
306
|
|
@@ -340,8 +350,6 @@ module Puma
|
|
340
350
|
|
341
351
|
@restart_dir ||= Dir.pwd
|
342
352
|
|
343
|
-
require 'rubygems'
|
344
|
-
|
345
353
|
# if $0 is a file in the current directory, then restart
|
346
354
|
# it the same, otherwise add -S on there because it was
|
347
355
|
# picked up in PATH.
|
@@ -352,9 +360,10 @@ module Puma
|
|
352
360
|
arg0 = [Gem.ruby, "-S", $0]
|
353
361
|
end
|
354
362
|
|
355
|
-
# Detect and reinject -Ilib from the command line
|
363
|
+
# Detect and reinject -Ilib from the command line, used for testing without bundler
|
364
|
+
# cruby has an expanded path, jruby has just "lib"
|
356
365
|
lib = File.expand_path "lib"
|
357
|
-
arg0[1,0] = ["-I", lib] if
|
366
|
+
arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
|
358
367
|
|
359
368
|
if defined? Puma::WILD_ARGS
|
360
369
|
@restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
|
@@ -384,12 +393,28 @@ module Puma
|
|
384
393
|
|
385
394
|
begin
|
386
395
|
Signal.trap "SIGTERM" do
|
387
|
-
|
396
|
+
graceful_stop
|
397
|
+
|
398
|
+
raise SignalException, "SIGTERM"
|
388
399
|
end
|
389
400
|
rescue Exception
|
390
401
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
391
402
|
end
|
392
403
|
|
404
|
+
begin
|
405
|
+
Signal.trap "SIGINT" do
|
406
|
+
if Puma.jruby?
|
407
|
+
@status = :exit
|
408
|
+
graceful_stop
|
409
|
+
exit
|
410
|
+
end
|
411
|
+
|
412
|
+
stop
|
413
|
+
end
|
414
|
+
rescue Exception
|
415
|
+
log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
|
416
|
+
end
|
417
|
+
|
393
418
|
begin
|
394
419
|
Signal.trap "SIGHUP" do
|
395
420
|
if @runner.redirected_io?
|
@@ -401,14 +426,6 @@ module Puma
|
|
401
426
|
rescue Exception
|
402
427
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
403
428
|
end
|
404
|
-
|
405
|
-
if Puma.jruby?
|
406
|
-
Signal.trap("INT") do
|
407
|
-
@status = :exit
|
408
|
-
graceful_stop
|
409
|
-
exit
|
410
|
-
end
|
411
|
-
end
|
412
429
|
end
|
413
430
|
end
|
414
431
|
end
|