puma 4.3.10 → 5.0.0.beta1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +58 -31
  3. data/LICENSE +23 -20
  4. data/README.md +17 -11
  5. data/docs/deployment.md +3 -1
  6. data/docs/fork_worker.md +31 -0
  7. data/docs/jungle/README.md +13 -0
  8. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  9. data/{tools → docs}/jungle/rc.d/puma +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  11. data/{tools → docs}/jungle/upstart/README.md +0 -0
  12. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  13. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  14. data/docs/signals.md +1 -0
  15. data/docs/systemd.md +1 -63
  16. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  17. data/ext/puma_http11/extconf.rb +3 -2
  18. data/ext/puma_http11/http11_parser.c +11 -26
  19. data/ext/puma_http11/http11_parser.rl +1 -3
  20. data/ext/puma_http11/http11_parser_common.rl +1 -1
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +46 -48
  23. data/ext/puma_http11/puma_http11.c +2 -38
  24. data/lib/puma/app/status.rb +16 -5
  25. data/lib/puma/binder.rb +62 -60
  26. data/lib/puma/cli.rb +7 -15
  27. data/lib/puma/client.rb +35 -32
  28. data/lib/puma/cluster.rb +179 -74
  29. data/lib/puma/configuration.rb +30 -42
  30. data/lib/puma/const.rb +2 -3
  31. data/lib/puma/control_cli.rb +27 -17
  32. data/lib/puma/detect.rb +8 -0
  33. data/lib/puma/dsl.rb +70 -34
  34. data/lib/puma/io_buffer.rb +9 -2
  35. data/lib/puma/jruby_restart.rb +0 -58
  36. data/lib/puma/launcher.rb +41 -29
  37. data/lib/puma/minissl.rb +13 -8
  38. data/lib/puma/null_io.rb +1 -1
  39. data/lib/puma/plugin.rb +1 -10
  40. data/lib/puma/rack/builder.rb +0 -4
  41. data/lib/puma/reactor.rb +6 -1
  42. data/lib/puma/runner.rb +5 -34
  43. data/lib/puma/server.rb +70 -190
  44. data/lib/puma/single.rb +7 -64
  45. data/lib/puma/state_file.rb +5 -2
  46. data/lib/puma/thread_pool.rb +85 -47
  47. data/lib/puma.rb +4 -0
  48. data/lib/rack/handler/puma.rb +1 -3
  49. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  50. metadata +22 -26
  51. data/docs/tcp_mode.md +0 -96
  52. data/ext/puma_http11/io_buffer.c +0 -155
  53. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  54. data/lib/puma/tcp_logger.rb +0 -41
  55. data/tools/jungle/README.md +0 -19
  56. data/tools/jungle/init.d/README.md +0 -61
  57. data/tools/jungle/init.d/puma +0 -421
  58. data/tools/jungle/init.d/run-puma +0 -18
data/lib/puma/server.rb CHANGED
@@ -11,6 +11,7 @@ require 'puma/client'
11
11
  require 'puma/binder'
12
12
  require 'puma/accept_nonblock'
13
13
  require 'puma/util'
14
+ require 'puma/io_buffer'
14
15
 
15
16
  require 'puma/puma_http11'
16
17
 
@@ -36,6 +37,7 @@ module Puma
36
37
 
37
38
  attr_reader :thread
38
39
  attr_reader :events
40
+ attr_reader :requests_count
39
41
  attr_accessor :app
40
42
 
41
43
  attr_accessor :min_threads
@@ -57,8 +59,7 @@ module Puma
57
59
  @app = app
58
60
  @events = events
59
61
 
60
- @check, @notify = Puma::Util.pipe
61
-
62
+ @check, @notify = nil
62
63
  @status = :stop
63
64
 
64
65
  @min_threads = 0
@@ -85,20 +86,18 @@ module Puma
85
86
  @mode = :http
86
87
 
87
88
  @precheck_closing = true
89
+
90
+ @requests_count = 0
88
91
  end
89
92
 
90
93
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
94
 
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
95
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
93
96
 
94
97
  def inherit_binder(bind)
95
98
  @binder = bind
96
99
  end
97
100
 
98
- def tcp_mode!
99
- @mode = :tcp
100
- end
101
-
102
101
  # On Linux, use TCP_CORK to better control how the TCP stack
103
102
  # packetizes our stream. This improves both latency and throughput.
104
103
  #
@@ -172,107 +171,6 @@ module Puma
172
171
  @thread_pool and @thread_pool.pool_capacity
173
172
  end
174
173
 
175
- # Lopez Mode == raw tcp apps
176
-
177
- def run_lopez_mode(background=true)
178
- @thread_pool = ThreadPool.new(@min_threads,
179
- @max_threads,
180
- Hash) do |client, tl|
181
-
182
- io = client.to_io
183
- addr = io.peeraddr.last
184
-
185
- if addr.empty?
186
- # Set unix socket addrs to localhost
187
- addr = "127.0.0.1:0"
188
- else
189
- addr = "#{addr}:#{io.peeraddr[1]}"
190
- end
191
-
192
- env = { 'thread' => tl, REMOTE_ADDR => addr }
193
-
194
- begin
195
- @app.call env, client.to_io
196
- rescue Object => e
197
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
198
- STDERR.puts e.backtrace
199
- end
200
-
201
- client.close unless env['detach']
202
- end
203
-
204
- @events.fire :state, :running
205
-
206
- if background
207
- @thread = Thread.new do
208
- Puma.set_thread_name "server"
209
- handle_servers_lopez_mode
210
- end
211
- return @thread
212
- else
213
- handle_servers_lopez_mode
214
- end
215
- end
216
-
217
- def handle_servers_lopez_mode
218
- begin
219
- check = @check
220
- sockets = [check] + @binder.ios
221
- pool = @thread_pool
222
-
223
- while @status == :run
224
- begin
225
- ios = IO.select sockets
226
- ios.first.each do |sock|
227
- if sock == check
228
- break if handle_check
229
- else
230
- begin
231
- if io = sock.accept_nonblock
232
- client = Client.new io, nil
233
- pool << client
234
- end
235
- rescue SystemCallError
236
- # nothing
237
- rescue Errno::ECONNABORTED
238
- # client closed the socket even before accept
239
- begin
240
- io.close
241
- rescue
242
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
- end
244
- end
245
- end
246
- end
247
- rescue Object => e
248
- @events.unknown_error self, e, "Listen loop"
249
- end
250
- end
251
-
252
- @events.fire :state, @status
253
-
254
- graceful_shutdown if @status == :stop || @status == :restart
255
-
256
- rescue Exception => e
257
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
258
- STDERR.puts e.backtrace
259
- ensure
260
- begin
261
- @check.close
262
- rescue
263
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
- end
265
-
266
- # Prevent can't modify frozen IOError (RuntimeError)
267
- begin
268
- @notify.close
269
- rescue IOError
270
- # no biggy
271
- end
272
- end
273
-
274
- @events.fire :state, :done
275
- end
276
174
  # Runs the server.
277
175
  #
278
176
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +184,9 @@ module Puma
286
184
 
287
185
  @status = :run
288
186
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
187
  @thread_pool = ThreadPool.new(@min_threads,
296
188
  @max_threads,
297
- IOBuffer) do |client, buffer|
189
+ ::Puma::IOBuffer) do |client, buffer|
298
190
 
299
191
  # Advertise this server into the thread
300
192
  Thread.current[ThreadLocalKey] = self
@@ -302,10 +194,10 @@ module Puma
302
194
  process_now = false
303
195
 
304
196
  begin
305
- if queue_requests
197
+ if @queue_requests
306
198
  process_now = client.eagerly_finish
307
199
  else
308
- client.finish
200
+ client.finish(@first_data_timeout)
309
201
  process_now = true
310
202
  end
311
203
  rescue MiniSSL::SSLError => e
@@ -331,11 +223,14 @@ module Puma
331
223
  @reactor.add client
332
224
  end
333
225
  end
226
+
227
+ process_now
334
228
  end
335
229
 
230
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
231
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
232
 
338
- if queue_requests
233
+ if @queue_requests
339
234
  @reactor = Reactor.new self, @thread_pool
340
235
  @reactor.run_in_thread
341
236
  end
@@ -362,6 +257,7 @@ module Puma
362
257
  end
363
258
 
364
259
  def handle_servers
260
+ @check, @notify = Puma::Util.pipe unless @notify
365
261
  begin
366
262
  check = @check
367
263
  sockets = [check] + @binder.ios
@@ -386,6 +282,10 @@ module Puma
386
282
  break if handle_check
387
283
  else
388
284
  begin
285
+ pool.wait_until_not_full
286
+ pool.wait_for_less_busy_worker(
287
+ @options[:wait_for_less_busy_worker].to_f)
288
+
389
289
  if io = sock.accept_nonblock
390
290
  client = Client.new io, @binder.env(sock)
391
291
  if remote_addr_value
@@ -395,10 +295,6 @@ module Puma
395
295
  end
396
296
 
397
297
  pool << client
398
- busy_threads = pool.wait_until_not_full
399
- if busy_threads == 0
400
- @options[:out_of_band].each(&:call) if @options[:out_of_band]
401
- end
402
298
  end
403
299
  rescue SystemCallError
404
300
  # nothing
@@ -419,17 +315,20 @@ module Puma
419
315
 
420
316
  @events.fire :state, @status
421
317
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
318
  if queue_requests
319
+ @queue_requests = false
424
320
  @reactor.clear!
425
321
  @reactor.shutdown
426
322
  end
323
+ graceful_shutdown if @status == :stop || @status == :restart
427
324
  rescue Exception => e
428
325
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
326
  STDERR.puts e.backtrace
430
327
  ensure
431
- @check.close
328
+ @check.close unless @check.closed? # Ruby 2.2 issue
432
329
  @notify.close
330
+ @notify = nil
331
+ @check = nil
433
332
  end
434
333
 
435
334
  @events.fire :state, :done
@@ -476,29 +375,24 @@ module Puma
476
375
  close_socket = false
477
376
  return
478
377
  when true
479
- return unless @queue_requests
480
378
  buffer.reset
481
379
 
482
380
  ThreadPool.clean_thread_locals if clean_thread_locals
483
381
 
484
382
  requests += 1
485
383
 
486
- # Closing keepalive sockets after they've made a reasonable
487
- # number of requests allows Puma to service many connections
488
- # fairly, even when the number of concurrent connections exceeds
489
- # the size of the threadpool. It also allows cluster mode Pumas
490
- # to keep load evenly distributed across workers, because clients
491
- # are randomly assigned a new worker when opening a new connection.
492
- #
493
- # Previously, Puma would kick connections in this conditional back
494
- # to the reactor. However, because this causes the todo set to increase
495
- # in size, the wait_until_full mutex would never unlock, leaving
496
- # any additional connections unserviced.
497
- break if requests >= MAX_FAST_INLINE
498
-
499
384
  check_for_more_data = @status == :run
500
385
 
386
+ if requests >= MAX_FAST_INLINE
387
+ # This will mean that reset will only try to use the data it already
388
+ # has buffered and won't try to read more data. What this means is that
389
+ # every client, independent of their request speed, gets treated like a slow
390
+ # one once every MAX_FAST_INLINE requests.
391
+ check_for_more_data = false
392
+ end
393
+
501
394
  unless client.reset(check_for_more_data)
395
+ return unless @queue_requests
502
396
  close_socket = false
503
397
  client.set_timeout @persistent_timeout
504
398
  @reactor.add client
@@ -631,6 +525,8 @@ module Puma
631
525
  #
632
526
  # Finally, it'll return +true+ on keep-alive connections.
633
527
  def handle_request(req, lines)
528
+ @requests_count +=1
529
+
634
530
  env = req.env
635
531
  client = req.io
636
532
 
@@ -677,37 +573,6 @@ module Puma
677
573
  }
678
574
  end
679
575
 
680
- # Fixup any headers with , in the name to have _ now. We emit
681
- # headers with , in them during the parse phase to avoid ambiguity
682
- # with the - to _ conversion for critical headers. But here for
683
- # compatibility, we'll convert them back. This code is written to
684
- # avoid allocation in the common case (ie there are no headers
685
- # with , in their names), that's why it has the extra conditionals.
686
-
687
- to_delete = nil
688
- to_add = nil
689
-
690
- env.each do |k,v|
691
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
692
- if to_delete
693
- to_delete << k
694
- else
695
- to_delete = [k]
696
- end
697
-
698
- unless to_add
699
- to_add = {}
700
- end
701
-
702
- to_add[k.tr(",", "_")] = v
703
- end
704
- end
705
-
706
- if to_delete
707
- to_delete.each { |k| env.delete(k) }
708
- env.merge! to_add
709
- end
710
-
711
576
  # A rack extension. If the app writes #call'ables to this
712
577
  # array, we will invoke them when the request is done.
713
578
  #
@@ -729,17 +594,14 @@ module Puma
729
594
  return :async
730
595
  end
731
596
  rescue ThreadPool::ForceShutdown => e
732
- @events.log "Detected force shutdown of a thread, returning 503"
733
- @events.unknown_error self, e, "Rack app"
734
-
735
- status = 503
736
- headers = {}
737
- res_body = ["Request was internally terminated early\n"]
597
+ @events.unknown_error self, e, "Rack app", env
598
+ @events.log "Detected force shutdown of a thread"
738
599
 
600
+ status, headers, res_body = lowlevel_error(e, env, 503)
739
601
  rescue Exception => e
740
602
  @events.unknown_error self, e, "Rack app", env
741
603
 
742
- status, headers, res_body = lowlevel_error(e, env)
604
+ status, headers, res_body = lowlevel_error(e, env, 500)
743
605
  end
744
606
 
745
607
  content_length = nil
@@ -754,10 +616,10 @@ module Puma
754
616
  line_ending = LINE_END
755
617
  colon = COLON
756
618
 
757
- http_11 = if env[HTTP_VERSION] == HTTP_11
619
+ http_11 = env[HTTP_VERSION] == HTTP_11
620
+ if http_11
758
621
  allow_chunked = true
759
622
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
760
- include_keepalive_header = false
761
623
 
762
624
  # An optimization. The most common response is 200, so we can
763
625
  # reply with the proper 200 status without having to compute
@@ -771,11 +633,9 @@ module Puma
771
633
 
772
634
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
773
635
  end
774
- true
775
636
  else
776
637
  allow_chunked = false
777
638
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
778
- include_keepalive_header = keep_alive
779
639
 
780
640
  # Same optimization as above for HTTP/1.1
781
641
  #
@@ -787,9 +647,12 @@ module Puma
787
647
 
788
648
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
789
649
  end
790
- false
791
650
  end
792
651
 
652
+ # regardless of what the client wants, we always close the connection
653
+ # if running without request queueing
654
+ keep_alive &&= @queue_requests
655
+
793
656
  response_hijack = nil
794
657
 
795
658
  headers.each do |k, vs|
@@ -816,10 +679,15 @@ module Puma
816
679
  end
817
680
  end
818
681
 
819
- if include_keepalive_header
820
- lines << CONNECTION_KEEP_ALIVE
821
- elsif http_11 && !keep_alive
822
- lines << CONNECTION_CLOSE
682
+ # HTTP/1.1 & 1.0 assume different defaults:
683
+ # - HTTP 1.0 assumes the connection will be closed if not specified
684
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
685
+ # Only set the header if we're doing something which is not the default
686
+ # for this protocol version
687
+ if http_11
688
+ lines << CONNECTION_CLOSE if !keep_alive
689
+ else
690
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
823
691
  end
824
692
 
825
693
  if no_body
@@ -946,19 +814,21 @@ module Puma
946
814
 
947
815
  # A fallback rack response if +@app+ raises as exception.
948
816
  #
949
- def lowlevel_error(e, env)
817
+ def lowlevel_error(e, env, status=500)
950
818
  if handler = @options[:lowlevel_error_handler]
951
819
  if handler.arity == 1
952
820
  return handler.call(e)
953
- else
821
+ elsif handler.arity == 2
954
822
  return handler.call(e, env)
823
+ else
824
+ return handler.call(e, env, status)
955
825
  end
956
826
  end
957
827
 
958
828
  if @leak_stack_on_error
959
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
829
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
960
830
  else
961
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
831
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
962
832
  end
963
833
  end
964
834
 
@@ -1016,6 +886,7 @@ module Puma
1016
886
  end
1017
887
 
1018
888
  def notify_safely(message)
889
+ @check, @notify = Puma::Util.pipe unless @notify
1019
890
  begin
1020
891
  @notify << message
1021
892
  rescue IOError
@@ -1045,8 +916,9 @@ module Puma
1045
916
  @thread.join if @thread && sync
1046
917
  end
1047
918
 
1048
- def begin_restart
919
+ def begin_restart(sync=false)
1049
920
  notify_safely(RESTART_COMMAND)
921
+ @thread.join if @thread && sync
1050
922
  end
1051
923
 
1052
924
  def fast_write(io, str)
@@ -1084,5 +956,13 @@ module Puma
1084
956
  HTTP_INJECTION_REGEX =~ header_value.to_s
1085
957
  end
1086
958
  private :possible_header_injection?
959
+
960
+ # List of methods invoked by #stats.
961
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
962
+
963
+ # Returns a hash of stats about the running server for reporting purposes.
964
+ def stats
965
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
966
+ end
1087
967
  end
1088
968
  end
data/lib/puma/single.rb CHANGED
@@ -14,11 +14,9 @@ module Puma
14
14
  # that this inherits from.
15
15
  class Single < Runner
16
16
  def stats
17
- b = @server.backlog || 0
18
- r = @server.running || 0
19
- t = @server.pool_capacity || 0
20
- m = @server.max_threads || 0
21
- %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
17
+ {
18
+ started_at: @started_at.utc.iso8601
19
+ }.merge(@server.stats)
22
20
  end
23
21
 
24
22
  def restart
@@ -39,64 +37,10 @@ module Puma
39
37
  @server.stop(true) if @server
40
38
  end
41
39
 
42
- def jruby_daemon?
43
- daemon? and Puma.jruby?
44
- end
45
-
46
- def jruby_daemon_start
47
- require 'puma/jruby_restart'
48
- JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
49
- end
50
-
51
40
  def run
52
- already_daemon = false
53
-
54
- if jruby_daemon?
55
- require 'puma/jruby_restart'
56
-
57
- if JRubyRestart.daemon?
58
- # load and bind before redirecting IO so errors show up on stdout/stderr
59
- load_and_bind
60
- redirect_io
61
- end
62
-
63
- already_daemon = JRubyRestart.daemon_init
64
- end
65
-
66
41
  output_header "single"
67
42
 
68
- if jruby_daemon?
69
- if already_daemon
70
- JRubyRestart.perm_daemonize
71
- else
72
- pid = nil
73
-
74
- Signal.trap "SIGUSR2" do
75
- log "* Started new process #{pid} as daemon..."
76
-
77
- # Must use exit! so we don't unwind and run the ensures
78
- # that will be run by the new child (such as deleting the
79
- # pidfile)
80
- exit!(true)
81
- end
82
-
83
- Signal.trap "SIGCHLD" do
84
- log "! Error starting new process as daemon, exiting"
85
- exit 1
86
- end
87
-
88
- jruby_daemon_start
89
- sleep
90
- end
91
- else
92
- if daemon?
93
- log "* Daemonizing..."
94
- Process.daemon(true)
95
- redirect_io
96
- end
97
-
98
- load_and_bind
99
- end
43
+ load_and_bind
100
44
 
101
45
  Plugins.fire_background
102
46
 
@@ -106,10 +50,9 @@ module Puma
106
50
 
107
51
  @server = server = start_server
108
52
 
109
- unless daemon?
110
- log "Use Ctrl-C to stop"
111
- redirect_io
112
- end
53
+
54
+ log "Use Ctrl-C to stop"
55
+ redirect_io
113
56
 
114
57
  @launcher.events.fire_on_booted!
115
58
 
@@ -8,8 +8,11 @@ module Puma
8
8
  @options = {}
9
9
  end
10
10
 
11
- def save(path)
12
- File.write path, YAML.dump(@options)
11
+ def save(path, permission = nil)
12
+ File.open(path, "w") do |file|
13
+ file.chmod(permission) if permission
14
+ file.write(YAML.dump(@options))
15
+ end
13
16
  end
14
17
 
15
18
  def load(path)