puma 5.0.2-java → 5.2.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +667 -567
  3. data/README.md +51 -21
  4. data/bin/puma-wild +3 -9
  5. data/docs/compile_options.md +19 -0
  6. data/docs/deployment.md +6 -7
  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/kubernetes.md +66 -0
  11. data/docs/nginx.md +1 -1
  12. data/docs/plugins.md +1 -1
  13. data/docs/restart.md +46 -23
  14. data/docs/stats.md +142 -0
  15. data/docs/systemd.md +25 -3
  16. data/ext/puma_http11/ext_help.h +1 -1
  17. data/ext/puma_http11/extconf.rb +18 -5
  18. data/ext/puma_http11/http11_parser.c +45 -47
  19. data/ext/puma_http11/http11_parser.java.rl +1 -1
  20. data/ext/puma_http11/http11_parser.rl +1 -1
  21. data/ext/puma_http11/mini_ssl.c +199 -119
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
  23. data/ext/puma_http11/puma_http11.c +25 -12
  24. data/lib/puma.rb +2 -2
  25. data/lib/puma/app/status.rb +44 -46
  26. data/lib/puma/binder.rb +69 -25
  27. data/lib/puma/cli.rb +4 -0
  28. data/lib/puma/client.rb +26 -79
  29. data/lib/puma/cluster.rb +37 -202
  30. data/lib/puma/cluster/worker.rb +176 -0
  31. data/lib/puma/cluster/worker_handle.rb +86 -0
  32. data/lib/puma/configuration.rb +21 -8
  33. data/lib/puma/const.rb +11 -3
  34. data/lib/puma/control_cli.rb +73 -70
  35. data/lib/puma/dsl.rb +100 -22
  36. data/lib/puma/error_logger.rb +10 -3
  37. data/lib/puma/events.rb +18 -3
  38. data/lib/puma/json.rb +96 -0
  39. data/lib/puma/launcher.rb +57 -15
  40. data/lib/puma/minissl.rb +47 -16
  41. data/lib/puma/minissl/context_builder.rb +6 -0
  42. data/lib/puma/null_io.rb +4 -0
  43. data/lib/puma/puma_http11.jar +0 -0
  44. data/lib/puma/queue_close.rb +26 -0
  45. data/lib/puma/reactor.rb +85 -363
  46. data/lib/puma/request.rb +451 -0
  47. data/lib/puma/runner.rb +17 -23
  48. data/lib/puma/server.rb +164 -553
  49. data/lib/puma/single.rb +2 -2
  50. data/lib/puma/state_file.rb +5 -3
  51. data/lib/puma/systemd.rb +46 -0
  52. data/lib/puma/util.rb +11 -0
  53. metadata +11 -6
  54. data/docs/jungle/upstart/README.md +0 -61
  55. data/docs/jungle/upstart/puma-manager.conf +0 -31
  56. data/docs/jungle/upstart/puma.conf +0 -69
  57. 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,12 @@ 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
+
88
94
  # @!attribute [r] inspect
89
95
  def inspect
90
96
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
@@ -103,7 +109,12 @@ module Puma
103
109
  end
104
110
 
105
111
  def set_timeout(val)
106
- @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
107
118
  end
108
119
 
109
120
  def reset(fast_check=true)
@@ -188,79 +199,20 @@ module Puma
188
199
  false
189
200
  end
190
201
 
191
- if IS_JRUBY
192
- def jruby_start_try_to_finish
193
- return read_body unless @read_header
194
-
195
- begin
196
- data = @io.sysread_nonblock(CHUNK_SIZE)
197
- rescue OpenSSL::SSL::SSLError => e
198
- return false if e.kind_of? IO::WaitReadable
199
- raise e
200
- end
201
-
202
- # No data means a closed socket
203
- unless data
204
- @buffer = nil
205
- set_ready
206
- raise EOFError
207
- end
208
-
209
- if @buffer
210
- @buffer << data
211
- else
212
- @buffer = data
213
- end
214
-
215
- @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
216
-
217
- if @parser.finished?
218
- return setup_body
219
- elsif @parsed_bytes >= MAX_HEADER
220
- raise HttpParserError,
221
- "HEADER is longer than allowed, aborting client early."
222
- end
223
-
224
- false
225
- end
226
-
227
- def eagerly_finish
228
- return true if @ready
229
-
230
- if @io.kind_of? OpenSSL::SSL::SSLSocket
231
- return true if jruby_start_try_to_finish
232
- end
233
-
234
- return false unless IO.select([@to_io], nil, nil, 0)
235
- try_to_finish
236
- end
237
-
238
- else
239
-
240
- def eagerly_finish
241
- return true if @ready
242
- return false unless IO.select([@to_io], nil, nil, 0)
243
- try_to_finish
244
- end
245
-
246
- # For documentation, see https://github.com/puma/puma/issues/1754
247
- send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
248
- 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
249
207
 
250
208
  def finish(timeout)
251
- return true if @ready
252
- until try_to_finish
253
- can_read = begin
254
- IO.select([@to_io], nil, nil, timeout)
255
- rescue ThreadPool::ForceShutdown
256
- nil
257
- end
258
- unless can_read
259
- write_error(408) if in_data_phase
260
- raise ConnectionError
261
- end
262
- end
263
- 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
264
216
  end
265
217
 
266
218
  def write_error(status_code)
@@ -287,13 +239,8 @@ module Puma
287
239
  # @version 5.0.0
288
240
  #
289
241
  def can_close?
290
- # Allow connection to close if it's received at least one full request
291
- # and hasn't received any data for a future request.
292
- #
293
- # From RFC 2616 section 8.1.4:
294
- # Servers SHOULD always respond to at least one request per connection,
295
- # if at all possible.
296
- @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
297
244
  end
298
245
 
299
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,7 +114,7 @@ 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
@@ -249,113 +174,25 @@ module Puma
249
174
  end
250
175
 
251
176
  def worker(index, master)
252
- title = "puma: cluster worker #{index}: #{master}"
253
- title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
254
- $0 = title
255
-
256
- Signal.trap "SIGINT", "IGNORE"
257
- Signal.trap "SIGCHLD", "DEFAULT"
258
-
259
- fork_worker = @options[:fork_worker] && index == 0
260
-
261
177
  @workers = []
262
- if !@options[:fork_worker] || fork_worker
263
- @master_read.close
264
- @suicide_pipe.close
265
- @fork_writer.close
266
- end
267
-
268
- Thread.new do
269
- Puma.set_thread_name "worker check pipe"
270
- IO.select [@check_pipe]
271
- log "! Detected parent died, dying"
272
- exit! 1
273
- end
274
178
 
275
- # If we're not running under a Bundler context, then
276
- # report the info about the context we will be using
277
- if !ENV['BUNDLE_GEMFILE']
278
- if File.exist?("Gemfile")
279
- log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
280
- elsif File.exist?("gems.rb")
281
- log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
282
- end
283
- end
284
-
285
- # Invoke any worker boot hooks so they can get
286
- # things in shape before booting the app.
287
- @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
288
-
289
- server = @server ||= start_server
290
- restart_server = Queue.new << true << false
291
-
292
- if fork_worker
293
- restart_server.clear
294
- worker_pids = []
295
- Signal.trap "SIGCHLD" do
296
- wakeup! if worker_pids.reject! do |p|
297
- Process.wait(p, Process::WNOHANG) rescue true
298
- end
299
- end
300
-
301
- Thread.new do
302
- Puma.set_thread_name "worker fork pipe"
303
- while (idx = @fork_pipe.gets)
304
- idx = idx.to_i
305
- if idx == -1 # stop server
306
- if restart_server.length > 0
307
- restart_server.clear
308
- server.begin_restart(true)
309
- @launcher.config.run_hooks :before_refork, nil, @launcher.events
310
- nakayoshi_gc
311
- end
312
- elsif idx == 0 # restart server
313
- restart_server << true << false
314
- else # fork worker
315
- worker_pids << pid = spawn_worker(idx, master)
316
- @worker_write << "f#{pid}:#{idx}\n" rescue nil
317
- end
318
- end
319
- end
320
- end
321
-
322
- Signal.trap "SIGTERM" do
323
- @worker_write << "e#{Process.pid}\n" rescue nil
324
- server.stop
325
- restart_server << false
326
- end
327
-
328
- begin
329
- @worker_write << "b#{Process.pid}:#{index}\n"
330
- rescue SystemCallError, IOError
331
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
332
- STDERR.puts "Master seems to have exited, exiting."
333
- return
334
- end
335
-
336
- Thread.new(@worker_write) do |io|
337
- Puma.set_thread_name "stat payload"
179
+ @master_read.close
180
+ @suicide_pipe.close
181
+ @fork_writer.close
338
182
 
339
- while true
340
- sleep Const::WORKER_CHECK_INTERVAL
341
- begin
342
- require 'json'
343
- io << "p#{Process.pid}#{server.stats.to_json}\n"
344
- rescue IOError
345
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
346
- break
347
- end
348
- 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
349
187
  end
350
188
 
351
- server.run.join while restart_server.pop
352
-
353
- # Invoke any worker shutdown hooks so they can prevent the worker
354
- # exiting until any background operations are completed
355
- @launcher.config.run_hooks :before_worker_shutdown, index, @launcher.events
356
- ensure
357
- @worker_write << "t#{Process.pid}\n" rescue nil
358
- @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
359
196
  end
360
197
 
361
198
  def restart
@@ -492,15 +329,19 @@ module Puma
492
329
 
493
330
  output_header "cluster"
494
331
 
495
- log "* Process workers: #{@options[:workers]}"
332
+ # This is aligned with the output from Runner, see Runner#output_header
333
+ log "* Workers: #{@options[:workers]}"
496
334
 
497
- 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) }
498
338
 
499
339
  if preload?
340
+ log "* Restarts: (\u2714) hot (\u2716) phased"
500
341
  log "* Preloading application"
501
342
  load_and_bind
502
343
 
503
- after = Thread.list
344
+ after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
504
345
 
505
346
  if after.size > before.size
506
347
  threads = (after - before)
@@ -514,7 +355,7 @@ module Puma
514
355
  end
515
356
  end
516
357
  else
517
- log "* Phased restart available"
358
+ log "* Restarts: (\u2714) hot (\u2714) phased"
518
359
 
519
360
  unless @launcher.config.app_configured?
520
361
  error "No application configured, nothing to run"
@@ -552,7 +393,7 @@ module Puma
552
393
  @master_read, @worker_write = read, @wakeup
553
394
 
554
395
  @launcher.config.run_hooks :before_fork, nil, @launcher.events
555
- nakayoshi_gc
396
+ Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
556
397
 
557
398
  spawn_workers
558
399
 
@@ -560,9 +401,9 @@ module Puma
560
401
  stop
561
402
  end
562
403
 
563
- @launcher.events.fire_on_booted!
564
-
565
404
  begin
405
+ booted = false
406
+
566
407
  while @status == :run
567
408
  begin
568
409
  if @phased_restart
@@ -593,7 +434,7 @@ module Puma
593
434
  case req
594
435
  when "b"
595
436
  w.boot!
596
- log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
437
+ log "- Worker #{w.index} (PID: #{pid}) booted, phase: #{w.phase}"
597
438
  @next_check = Time.now
598
439
  when "e"
599
440
  # external term, see worker method, Signal.trap "SIGTERM"
@@ -603,6 +444,10 @@ module Puma
603
444
  when "p"
604
445
  w.ping!(result.sub(/^\d+/,'').chomp)
605
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
606
451
  end
607
452
  else
608
453
  log "! Out-of-sync worker list, no #{pid} worker"
@@ -640,7 +485,9 @@ module Puma
640
485
  rescue Errno::ECHILD
641
486
  begin
642
487
  Process.kill(0, w.pid)
643
- 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
644
491
  rescue Errno::ESRCH, Errno::EPERM
645
492
  true # child is already terminated
646
493
  end
@@ -657,17 +504,5 @@ module Puma
657
504
  end
658
505
  end
659
506
  end
660
-
661
- # @version 5.0.0
662
- def nakayoshi_gc
663
- return unless @options[:nakayoshi_fork]
664
- log "! Promoting existing objects to old generation..."
665
- 4.times { GC.start(full_mark: false) }
666
- if GC.respond_to?(:compact)
667
- log "! Compacting..."
668
- GC.compact
669
- end
670
- log "! Friendly fork preparation complete."
671
- end
672
507
  end
673
508
  end