puma 5.0.0-java → 5.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1190 -574
  3. data/README.md +28 -20
  4. data/bin/puma-wild +3 -9
  5. data/docs/compile_options.md +19 -0
  6. data/docs/deployment.md +5 -6
  7. data/docs/fork_worker.md +2 -0
  8. data/docs/jungle/README.md +0 -4
  9. data/docs/jungle/rc.d/puma +2 -2
  10. data/docs/nginx.md +1 -1
  11. data/docs/restart.md +46 -23
  12. data/docs/systemd.md +25 -3
  13. data/ext/puma_http11/ext_help.h +1 -1
  14. data/ext/puma_http11/extconf.rb +4 -5
  15. data/ext/puma_http11/http11_parser.c +64 -64
  16. data/ext/puma_http11/mini_ssl.c +39 -37
  17. data/ext/puma_http11/puma_http11.c +25 -12
  18. data/lib/puma.rb +7 -4
  19. data/lib/puma/app/status.rb +44 -46
  20. data/lib/puma/binder.rb +48 -1
  21. data/lib/puma/cli.rb +4 -0
  22. data/lib/puma/client.rb +31 -80
  23. data/lib/puma/cluster.rb +39 -202
  24. data/lib/puma/cluster/worker.rb +176 -0
  25. data/lib/puma/cluster/worker_handle.rb +86 -0
  26. data/lib/puma/configuration.rb +20 -8
  27. data/lib/puma/const.rb +11 -3
  28. data/lib/puma/control_cli.rb +71 -70
  29. data/lib/puma/dsl.rb +67 -19
  30. data/lib/puma/error_logger.rb +2 -2
  31. data/lib/puma/events.rb +21 -3
  32. data/lib/puma/json.rb +96 -0
  33. data/lib/puma/launcher.rb +61 -12
  34. data/lib/puma/minissl.rb +8 -0
  35. data/lib/puma/puma_http11.jar +0 -0
  36. data/lib/puma/queue_close.rb +26 -0
  37. data/lib/puma/reactor.rb +79 -373
  38. data/lib/puma/request.rb +451 -0
  39. data/lib/puma/runner.rb +15 -21
  40. data/lib/puma/server.rb +193 -508
  41. data/lib/puma/single.rb +3 -2
  42. data/lib/puma/state_file.rb +5 -3
  43. data/lib/puma/systemd.rb +46 -0
  44. data/lib/puma/thread_pool.rb +22 -2
  45. data/lib/puma/util.rb +12 -0
  46. metadata +9 -6
  47. data/docs/jungle/upstart/README.md +0 -61
  48. data/docs/jungle/upstart/puma-manager.conf +0 -31
  49. data/docs/jungle/upstart/puma.conf +0 -69
  50. data/lib/puma/accept_nonblock.rb +0 -29
@@ -104,6 +104,10 @@ module Puma
104
104
  user_config.bind arg
105
105
  end
106
106
 
107
+ o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
108
+ user_config.bind_to_activated_sockets(arg || true)
109
+ end
110
+
107
111
  o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
108
112
  file_config.load arg
109
113
  end
@@ -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)
@@ -283,13 +239,8 @@ module Puma
283
239
  # @version 5.0.0
284
240
  #
285
241
  def can_close?
286
- # Allow connection to close if it's received at least one full request
287
- # and hasn't received any data for a future request.
288
- #
289
- # From RFC 2616 section 8.1.4:
290
- # Servers SHOULD always respond to at least one request per connection,
291
- # if at all possible.
292
- @requests_served > 0 && @parsed_bytes == 0
242
+ # Allow connection to close if we're not in the middle of parsing a request.
243
+ @parsed_bytes == 0
293
244
  end
294
245
 
295
246
  private
@@ -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,79 +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
-
81
- # @version 5.0.0
82
- attr_writer :pid, :phase
83
-
84
- def booted?
85
- @stage == :booted
86
- end
87
-
88
- def boot!
89
- @last_checkin = Time.now
90
- @stage = :booted
91
- end
92
-
93
- def term?
94
- @term
95
- end
96
-
97
- def ping!(status)
98
- @last_checkin = Time.now
99
- require 'json'
100
- @last_status = JSON.parse(status, symbolize_names: true)
101
- end
102
-
103
- # @see Puma::Cluster#check_workers
104
- # @version 5.0.0
105
- def ping_timeout
106
- @last_checkin +
107
- (booted? ?
108
- @options[:worker_timeout] :
109
- @options[:worker_boot_timeout]
110
- )
111
- end
112
-
113
- def term
114
- begin
115
- if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
116
- @signal = "KILL"
117
- else
118
- @term ||= true
119
- @first_term_sent ||= Time.now
120
- end
121
- Process.kill @signal, @pid if @pid
122
- rescue Errno::ESRCH
123
- end
124
- end
125
-
126
- def kill
127
- @signal = 'KILL'
128
- term
129
- end
130
-
131
- def hup
132
- Process.kill "HUP", @pid
133
- rescue Errno::ESRCH
134
- end
135
- end
136
-
137
62
  def spawn_workers
138
63
  diff = @options[:workers] - @workers.size
139
64
  return if diff < 1
@@ -154,7 +79,7 @@ module Puma
154
79
  end
155
80
 
156
81
  debug "Spawned worker: #{pid}"
157
- @workers << Worker.new(idx, pid, @phase, @options)
82
+ @workers << WorkerHandle.new(idx, pid, @phase, @options)
158
83
  end
159
84
 
160
85
  if @options[:fork_worker] &&
@@ -189,11 +114,12 @@ module Puma
189
114
  debug "Workers to cull: #{workers_to_cull.inspect}"
190
115
 
191
116
  workers_to_cull.each do |worker|
192
- log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
117
+ log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
193
118
  worker.term
194
119
  end
195
120
  end
196
121
 
122
+ # @!attribute [r] next_worker_index
197
123
  def next_worker_index
198
124
  all_positions = 0...@options[:workers]
199
125
  occupied_positions = @workers.map { |w| w.index }
@@ -248,113 +174,25 @@ module Puma
248
174
  end
249
175
 
250
176
  def worker(index, master)
251
- title = "puma: cluster worker #{index}: #{master}"
252
- title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
253
- $0 = title
254
-
255
- Signal.trap "SIGINT", "IGNORE"
256
- Signal.trap "SIGCHLD", "DEFAULT"
257
-
258
- fork_worker = @options[:fork_worker] && index == 0
259
-
260
177
  @workers = []
261
- if !@options[:fork_worker] || fork_worker
262
- @master_read.close
263
- @suicide_pipe.close
264
- @fork_writer.close
265
- end
266
-
267
- Thread.new do
268
- Puma.set_thread_name "worker check pipe"
269
- IO.select [@check_pipe]
270
- log "! Detected parent died, dying"
271
- exit! 1
272
- end
273
178
 
274
- # If we're not running under a Bundler context, then
275
- # report the info about the context we will be using
276
- if !ENV['BUNDLE_GEMFILE']
277
- if File.exist?("Gemfile")
278
- log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
279
- elsif File.exist?("gems.rb")
280
- log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
281
- end
282
- end
283
-
284
- # Invoke any worker boot hooks so they can get
285
- # things in shape before booting the app.
286
- @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
287
-
288
- server = @server ||= start_server
289
- restart_server = Queue.new << true << false
290
-
291
- if fork_worker
292
- restart_server.clear
293
- worker_pids = []
294
- Signal.trap "SIGCHLD" do
295
- wakeup! if worker_pids.reject! do |p|
296
- Process.wait(p, Process::WNOHANG) rescue true
297
- end
298
- end
299
-
300
- Thread.new do
301
- Puma.set_thread_name "worker fork pipe"
302
- while (idx = @fork_pipe.gets)
303
- idx = idx.to_i
304
- if idx == -1 # stop server
305
- if restart_server.length > 0
306
- restart_server.clear
307
- server.begin_restart(true)
308
- @launcher.config.run_hooks :before_refork, nil, @launcher.events
309
- nakayoshi_gc
310
- end
311
- elsif idx == 0 # restart server
312
- restart_server << true << false
313
- else # fork worker
314
- worker_pids << pid = spawn_worker(idx, master)
315
- @worker_write << "f#{pid}:#{idx}\n" rescue nil
316
- end
317
- end
318
- end
319
- end
320
-
321
- Signal.trap "SIGTERM" do
322
- @worker_write << "e#{Process.pid}\n" rescue nil
323
- server.stop
324
- restart_server << false
325
- end
326
-
327
- begin
328
- @worker_write << "b#{Process.pid}:#{index}\n"
329
- rescue SystemCallError, IOError
330
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
331
- STDERR.puts "Master seems to have exited, exiting."
332
- return
333
- end
334
-
335
- Thread.new(@worker_write) do |io|
336
- Puma.set_thread_name "stat payload"
179
+ @master_read.close
180
+ @suicide_pipe.close
181
+ @fork_writer.close
337
182
 
338
- while true
339
- sleep Const::WORKER_CHECK_INTERVAL
340
- begin
341
- require 'json'
342
- io << "p#{Process.pid}#{server.stats.to_json}\n"
343
- rescue IOError
344
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
345
- break
346
- end
347
- 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
348
187
  end
349
188
 
350
- server.run.join while restart_server.pop
351
-
352
- # Invoke any worker shutdown hooks so they can prevent the worker
353
- # exiting until any background operations are completed
354
- @launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
355
- ensure
356
- @worker_write << "t#{Process.pid}\n" rescue nil
357
- @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
358
196
  end
359
197
 
360
198
  def restart
@@ -396,6 +234,7 @@ module Puma
396
234
 
397
235
  # Inside of a child process, this will return all zeroes, as @workers is only populated in
398
236
  # the master process.
237
+ # @!attribute [r] stats
399
238
  def stats
400
239
  old_worker_count = @workers.count { |w| w.phase != @phase }
401
240
  worker_status = @workers.map do |w|
@@ -490,15 +329,19 @@ module Puma
490
329
 
491
330
  output_header "cluster"
492
331
 
493
- log "* Process workers: #{@options[:workers]}"
332
+ # This is aligned with the output from Runner, see Runner#output_header
333
+ log "* Workers: #{@options[:workers]}"
494
334
 
495
- before = Thread.list
335
+ # Threads explicitly marked as fork safe will be ignored.
336
+ # Used in Rails, but may be used by anyone.
337
+ before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
496
338
 
497
339
  if preload?
340
+ log "* Restarts: (\u2714) hot (\u2716) phased"
498
341
  log "* Preloading application"
499
342
  load_and_bind
500
343
 
501
- after = Thread.list
344
+ after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
502
345
 
503
346
  if after.size > before.size
504
347
  threads = (after - before)
@@ -512,7 +355,7 @@ module Puma
512
355
  end
513
356
  end
514
357
  else
515
- log "* Phased restart available"
358
+ log "* Restarts: (\u2714) hot (\u2714) phased"
516
359
 
517
360
  unless @launcher.config.app_configured?
518
361
  error "No application configured, nothing to run"
@@ -550,7 +393,7 @@ module Puma
550
393
  @master_read, @worker_write = read, @wakeup
551
394
 
552
395
  @launcher.config.run_hooks :before_fork, nil, @launcher.events
553
- nakayoshi_gc
396
+ Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
554
397
 
555
398
  spawn_workers
556
399
 
@@ -558,9 +401,9 @@ module Puma
558
401
  stop
559
402
  end
560
403
 
561
- @launcher.events.fire_on_booted!
562
-
563
404
  begin
405
+ booted = false
406
+
564
407
  while @status == :run
565
408
  begin
566
409
  if @phased_restart
@@ -591,7 +434,7 @@ module Puma
591
434
  case req
592
435
  when "b"
593
436
  w.boot!
594
- log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
437
+ log "- Worker #{w.index} (PID: #{pid}) booted, phase: #{w.phase}"
595
438
  @next_check = Time.now
596
439
  when "e"
597
440
  # external term, see worker method, Signal.trap "SIGTERM"
@@ -601,6 +444,10 @@ module Puma
601
444
  when "p"
602
445
  w.ping!(result.sub(/^\d+/,'').chomp)
603
446
  @launcher.events.fire(:ping!, w)
447
+ if !booted && @workers.none? {|worker| worker.last_status.empty?}
448
+ @launcher.events.fire_on_booted!
449
+ booted = true
450
+ end
604
451
  end
605
452
  else
606
453
  log "! Out-of-sync worker list, no #{pid} worker"
@@ -638,7 +485,9 @@ module Puma
638
485
  rescue Errno::ECHILD
639
486
  begin
640
487
  Process.kill(0, w.pid)
641
- false # child still alive, but has another parent
488
+ # child still alive but has another parent (e.g., using fork_worker)
489
+ w.term if w.term?
490
+ false
642
491
  rescue Errno::ESRCH, Errno::EPERM
643
492
  true # child is already terminated
644
493
  end
@@ -655,17 +504,5 @@ module Puma
655
504
  end
656
505
  end
657
506
  end
658
-
659
- # @version 5.0.0
660
- def nakayoshi_gc
661
- return unless @options[:nakayoshi_fork]
662
- log "! Promoting existing objects to old generation..."
663
- 4.times { GC.start(full_mark: false) }
664
- if GC.respond_to?(:compact)
665
- log "! Compacting..."
666
- GC.compact
667
- end
668
- log "! Friendly fork preparation complete."
669
- end
670
507
  end
671
508
  end