puma 5.0.0.beta2-java → 5.0.4-java
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 +4 -4
- data/History.md +1194 -570
- data/README.md +12 -5
- data/bin/puma-wild +3 -9
- data/docs/deployment.md +5 -6
- data/docs/jungle/README.md +0 -4
- data/docs/jungle/rc.d/puma +2 -2
- data/docs/nginx.md +1 -1
- data/docs/restart.md +46 -23
- data/docs/signals.md +3 -3
- data/docs/systemd.md +1 -1
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/mini_ssl.c +42 -37
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +40 -12
- data/ext/puma_http11/puma_http11.c +21 -10
- data/lib/puma.rb +15 -0
- data/lib/puma/app/status.rb +44 -43
- data/lib/puma/binder.rb +35 -8
- data/lib/puma/client.rb +32 -73
- data/lib/puma/cluster.rb +32 -191
- data/lib/puma/cluster/worker.rb +170 -0
- data/lib/puma/cluster/worker_handle.rb +83 -0
- data/lib/puma/configuration.rb +9 -7
- data/lib/puma/const.rb +2 -1
- data/lib/puma/control_cli.rb +2 -0
- data/lib/puma/detect.rb +9 -0
- data/lib/puma/dsl.rb +74 -36
- data/lib/puma/error_logger.rb +3 -2
- data/lib/puma/events.rb +7 -3
- data/lib/puma/launcher.rb +15 -8
- data/lib/puma/minissl.rb +28 -15
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/reactor.rb +77 -373
- data/lib/puma/request.rb +438 -0
- data/lib/puma/runner.rb +6 -18
- data/lib/puma/server.rb +192 -509
- data/lib/puma/single.rb +3 -2
- data/lib/puma/thread_pool.rb +27 -3
- data/lib/puma/util.rb +12 -0
- metadata +9 -8
- data/docs/jungle/upstart/README.md +0 -61
- data/docs/jungle/upstart/puma-manager.conf +0 -31
- data/docs/jungle/upstart/puma.conf +0 -69
- data/lib/puma/accept_nonblock.rb +0 -29
data/lib/puma/client.rb
CHANGED
@@ -85,6 +85,13 @@ module Puma
|
|
85
85
|
|
86
86
|
def_delegators :@io, :closed?
|
87
87
|
|
88
|
+
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
89
|
+
# used for MiniSSL::Socket
|
90
|
+
def io_ok?
|
91
|
+
@to_io.is_a?(::BasicSocket) && !closed?
|
92
|
+
end
|
93
|
+
|
94
|
+
# @!attribute [r] inspect
|
88
95
|
def inspect
|
89
96
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
90
97
|
end
|
@@ -96,12 +103,18 @@ module Puma
|
|
96
103
|
env[HIJACK_IO] ||= @io
|
97
104
|
end
|
98
105
|
|
106
|
+
# @!attribute [r] in_data_phase
|
99
107
|
def in_data_phase
|
100
108
|
!@read_header
|
101
109
|
end
|
102
110
|
|
103
111
|
def set_timeout(val)
|
104
|
-
@timeout_at =
|
112
|
+
@timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
|
113
|
+
end
|
114
|
+
|
115
|
+
# Number of seconds until the timeout elapses.
|
116
|
+
def timeout
|
117
|
+
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
105
118
|
end
|
106
119
|
|
107
120
|
def reset(fast_check=true)
|
@@ -155,7 +168,9 @@ module Puma
|
|
155
168
|
data = @io.read_nonblock(CHUNK_SIZE)
|
156
169
|
rescue IO::WaitReadable
|
157
170
|
return false
|
158
|
-
rescue
|
171
|
+
rescue EOFError
|
172
|
+
# Swallow error, don't log
|
173
|
+
rescue SystemCallError, IOError
|
159
174
|
raise ConnectionError, "Connection error detected during read"
|
160
175
|
end
|
161
176
|
|
@@ -184,79 +199,20 @@ module Puma
|
|
184
199
|
false
|
185
200
|
end
|
186
201
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
data = @io.sysread_nonblock(CHUNK_SIZE)
|
193
|
-
rescue OpenSSL::SSL::SSLError => e
|
194
|
-
return false if e.kind_of? IO::WaitReadable
|
195
|
-
raise e
|
196
|
-
end
|
197
|
-
|
198
|
-
# No data means a closed socket
|
199
|
-
unless data
|
200
|
-
@buffer = nil
|
201
|
-
set_ready
|
202
|
-
raise EOFError
|
203
|
-
end
|
204
|
-
|
205
|
-
if @buffer
|
206
|
-
@buffer << data
|
207
|
-
else
|
208
|
-
@buffer = data
|
209
|
-
end
|
210
|
-
|
211
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
212
|
-
|
213
|
-
if @parser.finished?
|
214
|
-
return setup_body
|
215
|
-
elsif @parsed_bytes >= MAX_HEADER
|
216
|
-
raise HttpParserError,
|
217
|
-
"HEADER is longer than allowed, aborting client early."
|
218
|
-
end
|
219
|
-
|
220
|
-
false
|
221
|
-
end
|
222
|
-
|
223
|
-
def eagerly_finish
|
224
|
-
return true if @ready
|
225
|
-
|
226
|
-
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
227
|
-
return true if jruby_start_try_to_finish
|
228
|
-
end
|
229
|
-
|
230
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
231
|
-
try_to_finish
|
232
|
-
end
|
233
|
-
|
234
|
-
else
|
235
|
-
|
236
|
-
def eagerly_finish
|
237
|
-
return true if @ready
|
238
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
239
|
-
try_to_finish
|
240
|
-
end
|
241
|
-
|
242
|
-
# For documentation, see https://github.com/puma/puma/issues/1754
|
243
|
-
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
244
|
-
end # IS_JRUBY
|
202
|
+
def eagerly_finish
|
203
|
+
return true if @ready
|
204
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
205
|
+
try_to_finish
|
206
|
+
end
|
245
207
|
|
246
208
|
def finish(timeout)
|
247
|
-
return
|
248
|
-
until try_to_finish
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
unless can_read
|
255
|
-
write_error(408) if in_data_phase
|
256
|
-
raise ConnectionError
|
257
|
-
end
|
258
|
-
end
|
259
|
-
true
|
209
|
+
return if @ready
|
210
|
+
IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
|
211
|
+
end
|
212
|
+
|
213
|
+
def timeout!
|
214
|
+
write_error(408) if in_data_phase
|
215
|
+
raise ConnectionError
|
260
216
|
end
|
261
217
|
|
262
218
|
def write_error(status_code)
|
@@ -280,6 +236,8 @@ module Puma
|
|
280
236
|
|
281
237
|
# Returns true if the persistent connection can be closed immediately
|
282
238
|
# without waiting for the configured idle/shutdown timeout.
|
239
|
+
# @version 5.0.0
|
240
|
+
#
|
283
241
|
def can_close?
|
284
242
|
# Allow connection to close if it's received at least one full request
|
285
243
|
# and hasn't received any data for a future request.
|
@@ -443,6 +401,7 @@ module Puma
|
|
443
401
|
end
|
444
402
|
end
|
445
403
|
|
404
|
+
# @version 5.0.0
|
446
405
|
def write_chunk(str)
|
447
406
|
@chunked_content_length += @body.write(str)
|
448
407
|
end
|
data/lib/puma/cluster.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'puma/runner'
|
4
4
|
require 'puma/util'
|
5
5
|
require 'puma/plugin'
|
6
|
+
require 'puma/cluster/worker_handle'
|
7
|
+
require 'puma/cluster/worker'
|
6
8
|
|
7
9
|
require 'time'
|
8
10
|
|
@@ -11,10 +13,6 @@ module Puma
|
|
11
13
|
# to boot and serve a Ruby application when puma "workers" are needed
|
12
14
|
# i.e. when using multi-processes. For example `$ puma -w 5`
|
13
15
|
#
|
14
|
-
# At the core of this class is running an instance of `Puma::Server` which
|
15
|
-
# gets created via the `start_server` method from the `Puma::Runner` class
|
16
|
-
# that this inherits from.
|
17
|
-
#
|
18
16
|
# An instance of this class will spawn the number of processes passed in
|
19
17
|
# via the `spawn_workers` method call. Each worker will have it's own
|
20
18
|
# instance of a `Puma::Server`.
|
@@ -61,75 +59,6 @@ module Puma
|
|
61
59
|
@workers.each { |x| x.hup }
|
62
60
|
end
|
63
61
|
|
64
|
-
class Worker
|
65
|
-
def initialize(idx, pid, phase, options)
|
66
|
-
@index = idx
|
67
|
-
@pid = pid
|
68
|
-
@phase = phase
|
69
|
-
@stage = :started
|
70
|
-
@signal = "TERM"
|
71
|
-
@options = options
|
72
|
-
@first_term_sent = nil
|
73
|
-
@started_at = Time.now
|
74
|
-
@last_checkin = Time.now
|
75
|
-
@last_status = {}
|
76
|
-
@term = false
|
77
|
-
end
|
78
|
-
|
79
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
|
80
|
-
attr_writer :pid, :phase
|
81
|
-
|
82
|
-
def booted?
|
83
|
-
@stage == :booted
|
84
|
-
end
|
85
|
-
|
86
|
-
def boot!
|
87
|
-
@last_checkin = Time.now
|
88
|
-
@stage = :booted
|
89
|
-
end
|
90
|
-
|
91
|
-
def term?
|
92
|
-
@term
|
93
|
-
end
|
94
|
-
|
95
|
-
def ping!(status)
|
96
|
-
@last_checkin = Time.now
|
97
|
-
require 'json'
|
98
|
-
@last_status = JSON.parse(status, symbolize_names: true)
|
99
|
-
end
|
100
|
-
|
101
|
-
def ping_timeout
|
102
|
-
@last_checkin +
|
103
|
-
(booted? ?
|
104
|
-
@options[:worker_timeout] :
|
105
|
-
@options[:worker_boot_timeout]
|
106
|
-
)
|
107
|
-
end
|
108
|
-
|
109
|
-
def term
|
110
|
-
begin
|
111
|
-
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
112
|
-
@signal = "KILL"
|
113
|
-
else
|
114
|
-
@term ||= true
|
115
|
-
@first_term_sent ||= Time.now
|
116
|
-
end
|
117
|
-
Process.kill @signal, @pid if @pid
|
118
|
-
rescue Errno::ESRCH
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def kill
|
123
|
-
@signal = 'KILL'
|
124
|
-
term
|
125
|
-
end
|
126
|
-
|
127
|
-
def hup
|
128
|
-
Process.kill "HUP", @pid
|
129
|
-
rescue Errno::ESRCH
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
62
|
def spawn_workers
|
134
63
|
diff = @options[:workers] - @workers.size
|
135
64
|
return if diff < 1
|
@@ -150,7 +79,7 @@ module Puma
|
|
150
79
|
end
|
151
80
|
|
152
81
|
debug "Spawned worker: #{pid}"
|
153
|
-
@workers <<
|
82
|
+
@workers << WorkerHandle.new(idx, pid, @phase, @options)
|
154
83
|
end
|
155
84
|
|
156
85
|
if @options[:fork_worker] &&
|
@@ -160,6 +89,7 @@ module Puma
|
|
160
89
|
end
|
161
90
|
end
|
162
91
|
|
92
|
+
# @version 5.0.0
|
163
93
|
def spawn_worker(idx, master)
|
164
94
|
@launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
|
165
95
|
|
@@ -189,6 +119,7 @@ module Puma
|
|
189
119
|
end
|
190
120
|
end
|
191
121
|
|
122
|
+
# @!attribute [r] next_worker_index
|
192
123
|
def next_worker_index
|
193
124
|
all_positions = 0...@options[:workers]
|
194
125
|
occupied_positions = @workers.map { |w| w.index }
|
@@ -243,113 +174,25 @@ module Puma
|
|
243
174
|
end
|
244
175
|
|
245
176
|
def worker(index, master)
|
246
|
-
title = "puma: cluster worker #{index}: #{master}"
|
247
|
-
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
248
|
-
$0 = title
|
249
|
-
|
250
|
-
Signal.trap "SIGINT", "IGNORE"
|
251
|
-
Signal.trap "SIGCHLD", "DEFAULT"
|
252
|
-
|
253
|
-
fork_worker = @options[:fork_worker] && index == 0
|
254
|
-
|
255
177
|
@workers = []
|
256
|
-
if !@options[:fork_worker] || fork_worker
|
257
|
-
@master_read.close
|
258
|
-
@suicide_pipe.close
|
259
|
-
@fork_writer.close
|
260
|
-
end
|
261
|
-
|
262
|
-
Thread.new do
|
263
|
-
Puma.set_thread_name "worker check pipe"
|
264
|
-
IO.select [@check_pipe]
|
265
|
-
log "! Detected parent died, dying"
|
266
|
-
exit! 1
|
267
|
-
end
|
268
178
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
if File.exist?("Gemfile")
|
273
|
-
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
274
|
-
elsif File.exist?("gems.rb")
|
275
|
-
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
# Invoke any worker boot hooks so they can get
|
280
|
-
# things in shape before booting the app.
|
281
|
-
@launcher.config.run_hooks :before_worker_boot, index, @launcher.events
|
179
|
+
@master_read.close
|
180
|
+
@suicide_pipe.close
|
181
|
+
@fork_writer.close
|
282
182
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
restart_server.clear
|
288
|
-
worker_pids = []
|
289
|
-
Signal.trap "SIGCHLD" do
|
290
|
-
wakeup! if worker_pids.reject! do |p|
|
291
|
-
Process.wait(p, Process::WNOHANG) rescue true
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
Thread.new do
|
296
|
-
Puma.set_thread_name "worker fork pipe"
|
297
|
-
while (idx = @fork_pipe.gets)
|
298
|
-
idx = idx.to_i
|
299
|
-
if idx == -1 # stop server
|
300
|
-
if restart_server.length > 0
|
301
|
-
restart_server.clear
|
302
|
-
server.begin_restart(true)
|
303
|
-
@launcher.config.run_hooks :before_refork, nil, @launcher.events
|
304
|
-
nakayoshi_gc
|
305
|
-
end
|
306
|
-
elsif idx == 0 # restart server
|
307
|
-
restart_server << true << false
|
308
|
-
else # fork worker
|
309
|
-
worker_pids << pid = spawn_worker(idx, master)
|
310
|
-
@worker_write << "f#{pid}:#{idx}\n" rescue nil
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
Signal.trap "SIGTERM" do
|
317
|
-
@worker_write << "e#{Process.pid}\n" rescue nil
|
318
|
-
server.stop
|
319
|
-
restart_server << false
|
320
|
-
end
|
321
|
-
|
322
|
-
begin
|
323
|
-
@worker_write << "b#{Process.pid}:#{index}\n"
|
324
|
-
rescue SystemCallError, IOError
|
325
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
326
|
-
STDERR.puts "Master seems to have exited, exiting."
|
327
|
-
return
|
328
|
-
end
|
329
|
-
|
330
|
-
Thread.new(@worker_write) do |io|
|
331
|
-
Puma.set_thread_name "stat payload"
|
332
|
-
|
333
|
-
while true
|
334
|
-
sleep Const::WORKER_CHECK_INTERVAL
|
335
|
-
begin
|
336
|
-
require 'json'
|
337
|
-
io << "p#{Process.pid}#{server.stats.to_json}\n"
|
338
|
-
rescue IOError
|
339
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
340
|
-
break
|
341
|
-
end
|
342
|
-
end
|
183
|
+
pipes = { check_pipe: @check_pipe, worker_write: @worker_write }
|
184
|
+
if @options[:fork_worker]
|
185
|
+
pipes[:fork_pipe] = @fork_pipe
|
186
|
+
pipes[:wakeup] = @wakeup
|
343
187
|
end
|
344
188
|
|
345
|
-
server
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
@worker_write.close
|
189
|
+
server = start_server if preload?
|
190
|
+
new_worker = Worker.new index: index,
|
191
|
+
master: master,
|
192
|
+
launcher: @launcher,
|
193
|
+
pipes: pipes,
|
194
|
+
server: server
|
195
|
+
new_worker.run
|
353
196
|
end
|
354
197
|
|
355
198
|
def restart
|
@@ -391,6 +234,7 @@ module Puma
|
|
391
234
|
|
392
235
|
# Inside of a child process, this will return all zeroes, as @workers is only populated in
|
393
236
|
# the master process.
|
237
|
+
# @!attribute [r] stats
|
394
238
|
def stats
|
395
239
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
396
240
|
worker_status = @workers.map do |w|
|
@@ -419,6 +263,7 @@ module Puma
|
|
419
263
|
@options[:preload_app]
|
420
264
|
end
|
421
265
|
|
266
|
+
# @version 5.0.0
|
422
267
|
def fork_worker!
|
423
268
|
if (worker = @workers.find { |w| w.index == 0 })
|
424
269
|
worker.phase += 1
|
@@ -544,7 +389,7 @@ module Puma
|
|
544
389
|
@master_read, @worker_write = read, @wakeup
|
545
390
|
|
546
391
|
@launcher.config.run_hooks :before_fork, nil, @launcher.events
|
547
|
-
nakayoshi_gc
|
392
|
+
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
548
393
|
|
549
394
|
spawn_workers
|
550
395
|
|
@@ -552,9 +397,9 @@ module Puma
|
|
552
397
|
stop
|
553
398
|
end
|
554
399
|
|
555
|
-
@launcher.events.fire_on_booted!
|
556
|
-
|
557
400
|
begin
|
401
|
+
booted = false
|
402
|
+
|
558
403
|
while @status == :run
|
559
404
|
begin
|
560
405
|
if @phased_restart
|
@@ -595,6 +440,10 @@ module Puma
|
|
595
440
|
when "p"
|
596
441
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
597
442
|
@launcher.events.fire(:ping!, w)
|
443
|
+
if !booted && @workers.none? {|worker| worker.last_status.empty?}
|
444
|
+
@launcher.events.fire_on_booted!
|
445
|
+
booted = true
|
446
|
+
end
|
598
447
|
end
|
599
448
|
else
|
600
449
|
log "! Out-of-sync worker list, no #{pid} worker"
|
@@ -632,7 +481,9 @@ module Puma
|
|
632
481
|
rescue Errno::ECHILD
|
633
482
|
begin
|
634
483
|
Process.kill(0, w.pid)
|
635
|
-
|
484
|
+
# child still alive but has another parent (e.g., using fork_worker)
|
485
|
+
w.term if w.term?
|
486
|
+
false
|
636
487
|
rescue Errno::ESRCH, Errno::EPERM
|
637
488
|
true # child is already terminated
|
638
489
|
end
|
@@ -640,6 +491,7 @@ module Puma
|
|
640
491
|
end
|
641
492
|
end
|
642
493
|
|
494
|
+
# @version 5.0.0
|
643
495
|
def timeout_workers
|
644
496
|
@workers.each do |w|
|
645
497
|
if !w.term? && w.ping_timeout <= Time.now
|
@@ -648,16 +500,5 @@ module Puma
|
|
648
500
|
end
|
649
501
|
end
|
650
502
|
end
|
651
|
-
|
652
|
-
def nakayoshi_gc
|
653
|
-
return unless @options[:nakayoshi_fork]
|
654
|
-
log "! Promoting existing objects to old generation..."
|
655
|
-
4.times { GC.start(full_mark: false) }
|
656
|
-
if GC.respond_to?(:compact)
|
657
|
-
log "! Compacting..."
|
658
|
-
GC.compact
|
659
|
-
end
|
660
|
-
log "! Friendly fork preparation complete."
|
661
|
-
end
|
662
503
|
end
|
663
504
|
end
|