puma 1.6.3 → 2.0.0.b1
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.
- data/History.txt +13 -0
- data/Manifest.txt +18 -0
- data/Rakefile +6 -0
- data/docs/config.md +0 -0
- data/docs/nginx.md +85 -0
- data/examples/puma/keystore.jks +0 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +3 -0
- data/ext/puma_http11/io_buffer.c +146 -0
- data/ext/puma_http11/mini_ssl.c +189 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +289 -0
- data/ext/puma_http11/puma_http11.c +6 -0
- data/lib/puma/accept_nonblock.rb +23 -0
- data/lib/puma/binder.rb +253 -0
- data/lib/puma/cli.rb +212 -114
- data/lib/puma/client.rb +28 -3
- data/lib/puma/configuration.rb +11 -4
- data/lib/puma/const.rb +12 -1
- data/lib/puma/delegation.rb +11 -0
- data/lib/puma/events.rb +18 -0
- data/lib/puma/io_buffer.rb +7 -0
- data/lib/puma/java_io_buffer.rb +45 -0
- data/lib/puma/minissl.rb +124 -0
- data/lib/puma/puma_http11.bundle +0 -0
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/reactor.rb +4 -1
- data/lib/puma/server.rb +67 -144
- data/lib/puma/thread_pool.rb +5 -2
- data/puma.gemspec +5 -5
- data/test/test_integration.rb +53 -2
- data/test/test_iobuffer.rb +38 -0
- data/test/test_puma_server.rb +12 -7
- data/tools/jungle/README.md +54 -0
- data/tools/jungle/puma +332 -0
- data/tools/jungle/run-puma +3 -0
- metadata +24 -5
data/lib/puma/cli.rb
CHANGED
@@ -4,6 +4,7 @@ require 'uri'
|
|
4
4
|
require 'puma/server'
|
5
5
|
require 'puma/const'
|
6
6
|
require 'puma/configuration'
|
7
|
+
require 'puma/binder'
|
7
8
|
require 'puma/detect'
|
8
9
|
|
9
10
|
require 'rack/commonlogger'
|
@@ -20,10 +21,13 @@ module Puma
|
|
20
21
|
# this object will report status on.
|
21
22
|
#
|
22
23
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
24
|
+
@debug = false
|
23
25
|
@argv = argv
|
24
26
|
@stdout = stdout
|
25
27
|
@stderr = stderr
|
26
28
|
|
29
|
+
@workers = []
|
30
|
+
|
27
31
|
@events = Events.new @stdout, @stderr
|
28
32
|
|
29
33
|
@server = nil
|
@@ -31,26 +35,12 @@ module Puma
|
|
31
35
|
|
32
36
|
@restart = false
|
33
37
|
|
34
|
-
@listeners = []
|
35
|
-
|
36
38
|
setup_options
|
37
39
|
|
38
40
|
generate_restart_data
|
39
41
|
|
40
|
-
@
|
41
|
-
|
42
|
-
|
43
|
-
ENV.each do |k,v|
|
44
|
-
if k =~ /PUMA_INHERIT_\d+/
|
45
|
-
fd, url = v.split(":", 2)
|
46
|
-
@inherited_fds[url] = fd.to_i
|
47
|
-
remove << k
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
remove.each do |k|
|
52
|
-
ENV.delete k
|
53
|
-
end
|
42
|
+
@binder = Binder.new(@events)
|
43
|
+
@binder.import_from_env
|
54
44
|
end
|
55
45
|
|
56
46
|
def restart_on_stop!
|
@@ -98,8 +88,8 @@ module Puma
|
|
98
88
|
blk.call self
|
99
89
|
end
|
100
90
|
|
101
|
-
if
|
102
|
-
@listeners.each_with_index do |(str,io),i|
|
91
|
+
if jruby?
|
92
|
+
@binder.listeners.each_with_index do |(str,io),i|
|
103
93
|
io.close
|
104
94
|
|
105
95
|
# We have to unlink a unix socket path that's not being used
|
@@ -113,7 +103,7 @@ module Puma
|
|
113
103
|
require 'puma/jruby_restart'
|
114
104
|
JRubyRestart.chdir_exec(@restart_dir, Gem.ruby, *@restart_argv)
|
115
105
|
else
|
116
|
-
@listeners.each_with_index do |(l,io),i|
|
106
|
+
@binder.listeners.each_with_index do |(l,io),i|
|
117
107
|
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
118
108
|
end
|
119
109
|
|
@@ -140,6 +130,26 @@ module Puma
|
|
140
130
|
@events.error str
|
141
131
|
end
|
142
132
|
|
133
|
+
def debug(str)
|
134
|
+
if @options[:debug]
|
135
|
+
@events.log "- #{str}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def jruby?
|
140
|
+
IS_JRUBY
|
141
|
+
end
|
142
|
+
|
143
|
+
def windows?
|
144
|
+
RUBY_PLATFORM =~ /mswin32|ming32/
|
145
|
+
end
|
146
|
+
|
147
|
+
def unsupported(str, cond=true)
|
148
|
+
return unless cond
|
149
|
+
@events.error str
|
150
|
+
raise UnsupportedOption
|
151
|
+
end
|
152
|
+
|
143
153
|
# Build the OptionParser object to handle the available options.
|
144
154
|
#
|
145
155
|
def setup_options
|
@@ -147,11 +157,13 @@ module Puma
|
|
147
157
|
:min_threads => 0,
|
148
158
|
:max_threads => 16,
|
149
159
|
:quiet => false,
|
150
|
-
:
|
160
|
+
:debug => false,
|
161
|
+
:binds => [],
|
162
|
+
:workers => 0
|
151
163
|
}
|
152
164
|
|
153
165
|
@parser = OptionParser.new do |o|
|
154
|
-
o.on "-b", "--bind URI", "URI to bind to (tcp
|
166
|
+
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
155
167
|
@options[:binds] << arg
|
156
168
|
end
|
157
169
|
|
@@ -176,6 +188,10 @@ module Puma
|
|
176
188
|
@options[:quiet] = true
|
177
189
|
end
|
178
190
|
|
191
|
+
o.on "--debug", "Log lowlevel debugging information" do
|
192
|
+
@options[:debug] = true
|
193
|
+
end
|
194
|
+
|
179
195
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
180
196
|
@options[:state] = arg
|
181
197
|
end
|
@@ -184,8 +200,8 @@ module Puma
|
|
184
200
|
"Use 'auto' to use temp unix server" do |arg|
|
185
201
|
if arg
|
186
202
|
@options[:control_url] = arg
|
187
|
-
elsif
|
188
|
-
|
203
|
+
elsif jruby?
|
204
|
+
unsupported "No default url available on JRuby"
|
189
205
|
end
|
190
206
|
end
|
191
207
|
|
@@ -205,6 +221,14 @@ module Puma
|
|
205
221
|
end
|
206
222
|
end
|
207
223
|
|
224
|
+
o.on "-w", "--workers COUNT",
|
225
|
+
"Activate cluster mode: How many worker processes to create" do |arg|
|
226
|
+
unsupported "-w not supported on JRuby and Windows",
|
227
|
+
jruby? || windows?
|
228
|
+
|
229
|
+
@options[:workers] = arg.to_i
|
230
|
+
end
|
231
|
+
|
208
232
|
o.on "--restart-cmd CMD",
|
209
233
|
"The puma command to run during a hot restart",
|
210
234
|
"Default: inferred" do |cmd|
|
@@ -291,19 +315,39 @@ module Puma
|
|
291
315
|
# for it to finish.
|
292
316
|
#
|
293
317
|
def run
|
294
|
-
|
318
|
+
begin
|
319
|
+
parse_options
|
320
|
+
rescue UnsupportedOption
|
321
|
+
exit 1
|
322
|
+
end
|
295
323
|
|
296
|
-
|
324
|
+
clustered = @options[:workers] > 0
|
325
|
+
|
326
|
+
if clustered
|
327
|
+
@events = PidEvents.new STDOUT, STDERR
|
328
|
+
@options[:logger] = @events
|
329
|
+
end
|
297
330
|
|
298
|
-
|
331
|
+
set_rack_environment
|
299
332
|
|
300
333
|
write_pid
|
301
334
|
write_state
|
302
335
|
|
336
|
+
@binder.parse @options[:binds], self
|
337
|
+
|
338
|
+
if clustered
|
339
|
+
run_cluster
|
340
|
+
else
|
341
|
+
run_single
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def run_single
|
303
346
|
min_t = @options[:min_threads]
|
304
347
|
max_t = @options[:max_threads]
|
305
348
|
|
306
|
-
server = Puma::Server.new app, @events
|
349
|
+
server = Puma::Server.new @config.app, @events
|
350
|
+
server.binder = @binder
|
307
351
|
server.min_threads = min_t
|
308
352
|
server.max_threads = max_t
|
309
353
|
|
@@ -311,93 +355,6 @@ module Puma
|
|
311
355
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
312
356
|
log "* Environment: #{ENV['RACK_ENV']}"
|
313
357
|
|
314
|
-
@options[:binds].each do |str|
|
315
|
-
uri = URI.parse str
|
316
|
-
case uri.scheme
|
317
|
-
when "tcp"
|
318
|
-
if fd = @inherited_fds.delete(str)
|
319
|
-
log "* Inherited #{str}"
|
320
|
-
io = server.inherit_tcp_listener uri.host, uri.port, fd
|
321
|
-
else
|
322
|
-
log "* Listening on #{str}"
|
323
|
-
io = server.add_tcp_listener uri.host, uri.port
|
324
|
-
end
|
325
|
-
|
326
|
-
@listeners << [str, io]
|
327
|
-
when "unix"
|
328
|
-
path = "#{uri.host}#{uri.path}"
|
329
|
-
|
330
|
-
if fd = @inherited_fds.delete(str)
|
331
|
-
log "* Inherited #{str}"
|
332
|
-
io = server.inherit_unix_listener path, fd
|
333
|
-
else
|
334
|
-
log "* Listening on #{str}"
|
335
|
-
|
336
|
-
umask = nil
|
337
|
-
|
338
|
-
if uri.query
|
339
|
-
params = Rack::Utils.parse_query uri.query
|
340
|
-
if u = params['umask']
|
341
|
-
# Use Integer() to respect the 0 prefix as octal
|
342
|
-
umask = Integer(u)
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
io = server.add_unix_listener path, umask
|
347
|
-
end
|
348
|
-
|
349
|
-
@listeners << [str, io]
|
350
|
-
when "ssl"
|
351
|
-
params = Rack::Utils.parse_query uri.query
|
352
|
-
require 'openssl'
|
353
|
-
|
354
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
355
|
-
unless params['key']
|
356
|
-
error "Please specify the SSL key via 'key='"
|
357
|
-
end
|
358
|
-
|
359
|
-
ctx.key = OpenSSL::PKey::RSA.new File.read(params['key'])
|
360
|
-
|
361
|
-
unless params['cert']
|
362
|
-
error "Please specify the SSL cert via 'cert='"
|
363
|
-
end
|
364
|
-
|
365
|
-
ctx.cert = OpenSSL::X509::Certificate.new File.read(params['cert'])
|
366
|
-
|
367
|
-
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
368
|
-
|
369
|
-
if fd = @inherited_fds.delete(str)
|
370
|
-
log "* Inherited #{str}"
|
371
|
-
io = server.inherited_ssl_listener fd, ctx
|
372
|
-
else
|
373
|
-
log "* Listening on #{str}"
|
374
|
-
io = server.add_ssl_listener uri.host, uri.port, ctx
|
375
|
-
end
|
376
|
-
|
377
|
-
@listeners << [str, io]
|
378
|
-
else
|
379
|
-
error "Invalid URI: #{str}"
|
380
|
-
end
|
381
|
-
end
|
382
|
-
|
383
|
-
# If we inherited fds but didn't use them (because of a
|
384
|
-
# configuration change), then be sure to close them.
|
385
|
-
@inherited_fds.each do |str, fd|
|
386
|
-
log "* Closing unused inherited connection: #{str}"
|
387
|
-
|
388
|
-
begin
|
389
|
-
IO.for_fd(fd).close
|
390
|
-
rescue SystemCallError
|
391
|
-
end
|
392
|
-
|
393
|
-
# We have to unlink a unix socket path that's not being used
|
394
|
-
uri = URI.parse str
|
395
|
-
if uri.scheme == "unix"
|
396
|
-
path = "#{uri.host}#{uri.path}"
|
397
|
-
File.unlink path
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
358
|
@server = server
|
402
359
|
|
403
360
|
if str = @options[:control_url]
|
@@ -465,6 +422,147 @@ module Puma
|
|
465
422
|
end
|
466
423
|
end
|
467
424
|
|
425
|
+
def worker
|
426
|
+
$0 = "puma: cluster worker: #{@master_pid}"
|
427
|
+
Signal.trap "SIGINT", "IGNORE"
|
428
|
+
|
429
|
+
@suicide_pipe.close
|
430
|
+
|
431
|
+
Thread.new do
|
432
|
+
IO.select [@check_pipe]
|
433
|
+
log "! Detected parent died, dieing"
|
434
|
+
exit! 1
|
435
|
+
end
|
436
|
+
|
437
|
+
min_t = @options[:min_threads]
|
438
|
+
max_t = @options[:max_threads]
|
439
|
+
|
440
|
+
server = Puma::Server.new @config.app, @events
|
441
|
+
server.min_threads = min_t
|
442
|
+
server.max_threads = max_t
|
443
|
+
server.binder = @binder
|
444
|
+
|
445
|
+
Signal.trap "SIGTERM" do
|
446
|
+
server.stop
|
447
|
+
end
|
448
|
+
|
449
|
+
server.run.join
|
450
|
+
end
|
451
|
+
|
452
|
+
def stop_workers
|
453
|
+
log "- Gracefully shutting down workers..."
|
454
|
+
@workers.each { |x| x.term }
|
455
|
+
|
456
|
+
begin
|
457
|
+
Process.waitall
|
458
|
+
rescue Interrupt
|
459
|
+
log "! Cancelled waiting for workers"
|
460
|
+
else
|
461
|
+
log "- Goodbye!"
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
class Worker
|
466
|
+
def initialize(pid)
|
467
|
+
@pid = pid
|
468
|
+
end
|
469
|
+
|
470
|
+
attr_reader :pid
|
471
|
+
|
472
|
+
def term
|
473
|
+
begin
|
474
|
+
Process.kill "TERM", @pid
|
475
|
+
rescue Errno::ESRCH
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def spawn_workers
|
481
|
+
diff = @options[:workers] - @workers.size
|
482
|
+
|
483
|
+
diff.times do
|
484
|
+
pid = fork { worker }
|
485
|
+
debug "Spawned worker: #{pid}"
|
486
|
+
@workers << Worker.new(pid)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def check_workers
|
491
|
+
while true
|
492
|
+
pid = Process.waitpid(-1, Process::WNOHANG)
|
493
|
+
break unless pid
|
494
|
+
|
495
|
+
@workers.delete_if { |w| w.pid == pid }
|
496
|
+
end
|
497
|
+
|
498
|
+
spawn_workers
|
499
|
+
end
|
500
|
+
|
501
|
+
def run_cluster
|
502
|
+
log "Puma #{Puma::Const::PUMA_VERSION} starting in cluster mode..."
|
503
|
+
log "* Process workers: #{@options[:workers]}"
|
504
|
+
log "* Min threads: #{@options[:min_threads]}, max threads: #{@options[:max_threads]}"
|
505
|
+
log "* Environment: #{ENV['RACK_ENV']}"
|
506
|
+
|
507
|
+
@master_pid = Process.pid
|
508
|
+
|
509
|
+
read, write = IO.pipe
|
510
|
+
|
511
|
+
Signal.trap "SIGCHLD" do
|
512
|
+
write.write "!"
|
513
|
+
end
|
514
|
+
|
515
|
+
stop = false
|
516
|
+
|
517
|
+
begin
|
518
|
+
Signal.trap "SIGUSR2" do
|
519
|
+
@restart = true
|
520
|
+
stop = true
|
521
|
+
write.write "!"
|
522
|
+
end
|
523
|
+
rescue Exception
|
524
|
+
end
|
525
|
+
|
526
|
+
begin
|
527
|
+
Signal.trap "SIGTERM" do
|
528
|
+
stop = true
|
529
|
+
write.write "!"
|
530
|
+
end
|
531
|
+
rescue Exception
|
532
|
+
end
|
533
|
+
|
534
|
+
# Used by the workers to detect if the master process dies.
|
535
|
+
# If select says that @check_pipe is ready, it's because the
|
536
|
+
# master has exited and @suicide_pipe has been automatically
|
537
|
+
# closed.
|
538
|
+
#
|
539
|
+
@check_pipe, @suicide_pipe = IO.pipe
|
540
|
+
|
541
|
+
spawn_workers
|
542
|
+
|
543
|
+
log "* Use Ctrl-C to stop"
|
544
|
+
|
545
|
+
begin
|
546
|
+
while !stop
|
547
|
+
begin
|
548
|
+
IO.select([read], nil, nil, 5)
|
549
|
+
check_workers
|
550
|
+
rescue Interrupt
|
551
|
+
stop = true
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
stop_workers
|
556
|
+
ensure
|
557
|
+
delete_pidfile
|
558
|
+
end
|
559
|
+
|
560
|
+
if @restart
|
561
|
+
log "* Restarting..."
|
562
|
+
restart!
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
468
566
|
def stop
|
469
567
|
@server.stop(true) if @server
|
470
568
|
delete_pidfile
|
data/lib/puma/client.rb
CHANGED
@@ -48,7 +48,7 @@ module Puma
|
|
48
48
|
@timeout_at = Time.now + val
|
49
49
|
end
|
50
50
|
|
51
|
-
def reset
|
51
|
+
def reset(fast_check=true)
|
52
52
|
@parser.reset
|
53
53
|
@read_header = true
|
54
54
|
@env = @proto_env.dup
|
@@ -67,6 +67,9 @@ module Puma
|
|
67
67
|
end
|
68
68
|
|
69
69
|
return false
|
70
|
+
elsif fast_check &&
|
71
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
72
|
+
return try_to_finish
|
70
73
|
end
|
71
74
|
end
|
72
75
|
|
@@ -124,7 +127,11 @@ module Puma
|
|
124
127
|
def try_to_finish
|
125
128
|
return read_body unless @read_header
|
126
129
|
|
127
|
-
|
130
|
+
begin
|
131
|
+
data = @io.read_nonblock(CHUNK_SIZE)
|
132
|
+
rescue Errno::EAGAIN
|
133
|
+
return false
|
134
|
+
end
|
128
135
|
|
129
136
|
if @buffer
|
130
137
|
@buffer << data
|
@@ -204,7 +211,11 @@ module Puma
|
|
204
211
|
want = remain
|
205
212
|
end
|
206
213
|
|
207
|
-
|
214
|
+
begin
|
215
|
+
chunk = @io.read_nonblock(want)
|
216
|
+
rescue Errno::EAGAIN
|
217
|
+
return false
|
218
|
+
end
|
208
219
|
|
209
220
|
# No chunk means a closed socket
|
210
221
|
unless chunk
|
@@ -229,5 +240,19 @@ module Puma
|
|
229
240
|
|
230
241
|
false
|
231
242
|
end
|
243
|
+
|
244
|
+
def write_400
|
245
|
+
begin
|
246
|
+
@io << ERROR_400_RESPONSE
|
247
|
+
rescue StandardError
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def write_500
|
252
|
+
begin
|
253
|
+
@io << ERROR_500_RESPONSE
|
254
|
+
rescue StandardError
|
255
|
+
end
|
256
|
+
end
|
232
257
|
end
|
233
258
|
end
|