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.

Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1194 -570
  3. data/README.md +12 -5
  4. data/bin/puma-wild +3 -9
  5. data/docs/deployment.md +5 -6
  6. data/docs/jungle/README.md +0 -4
  7. data/docs/jungle/rc.d/puma +2 -2
  8. data/docs/nginx.md +1 -1
  9. data/docs/restart.md +46 -23
  10. data/docs/signals.md +3 -3
  11. data/docs/systemd.md +1 -1
  12. data/ext/puma_http11/ext_help.h +1 -1
  13. data/ext/puma_http11/mini_ssl.c +42 -37
  14. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  15. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +40 -12
  16. data/ext/puma_http11/puma_http11.c +21 -10
  17. data/lib/puma.rb +15 -0
  18. data/lib/puma/app/status.rb +44 -43
  19. data/lib/puma/binder.rb +35 -8
  20. data/lib/puma/client.rb +32 -73
  21. data/lib/puma/cluster.rb +32 -191
  22. data/lib/puma/cluster/worker.rb +170 -0
  23. data/lib/puma/cluster/worker_handle.rb +83 -0
  24. data/lib/puma/configuration.rb +9 -7
  25. data/lib/puma/const.rb +2 -1
  26. data/lib/puma/control_cli.rb +2 -0
  27. data/lib/puma/detect.rb +9 -0
  28. data/lib/puma/dsl.rb +74 -36
  29. data/lib/puma/error_logger.rb +3 -2
  30. data/lib/puma/events.rb +7 -3
  31. data/lib/puma/launcher.rb +15 -8
  32. data/lib/puma/minissl.rb +28 -15
  33. data/lib/puma/minissl/context_builder.rb +0 -3
  34. data/lib/puma/puma_http11.jar +0 -0
  35. data/lib/puma/queue_close.rb +26 -0
  36. data/lib/puma/reactor.rb +77 -373
  37. data/lib/puma/request.rb +438 -0
  38. data/lib/puma/runner.rb +6 -18
  39. data/lib/puma/server.rb +192 -509
  40. data/lib/puma/single.rb +3 -2
  41. data/lib/puma/thread_pool.rb +27 -3
  42. data/lib/puma/util.rb +12 -0
  43. metadata +9 -8
  44. data/docs/jungle/upstart/README.md +0 -61
  45. data/docs/jungle/upstart/puma-manager.conf +0 -31
  46. data/docs/jungle/upstart/puma.conf +0 -69
  47. data/lib/puma/accept_nonblock.rb +0 -29
@@ -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 = Time.now + val
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 SystemCallError, IOError, EOFError
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
- if IS_JRUBY
188
- def jruby_start_try_to_finish
189
- return read_body unless @read_header
190
-
191
- begin
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 true if @ready
248
- until try_to_finish
249
- can_read = begin
250
- IO.select([@to_io], nil, nil, timeout)
251
- rescue ThreadPool::ForceShutdown
252
- nil
253
- end
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
@@ -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 << Worker.new(idx, pid, @phase, @options)
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
- # If we're not running under a Bundler context, then
270
- # report the info about the context we will be using
271
- if !ENV['BUNDLE_GEMFILE']
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
- server = @server ||= start_server
284
- restart_server = Queue.new << true << false
285
-
286
- if fork_worker
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.run.join while restart_server.pop
346
-
347
- # Invoke any worker shutdown hooks so they can prevent the worker
348
- # exiting until any background operations are completed
349
- @launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
350
- ensure
351
- @worker_write << "t#{Process.pid}\n" rescue nil
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
- false # child still alive, but has another parent
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