puma 3.12.6 → 5.3.2

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1400 -451
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +20 -10
  20. data/docs/rails_dev_mode.md +29 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +7 -6
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +48 -70
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +27 -0
  28. data/ext/puma_http11/http11_parser.c +81 -108
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +22 -38
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +3 -3
  33. data/ext/puma_http11/mini_ssl.c +254 -91
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  38. data/ext/puma_http11/puma_http11.c +34 -50
  39. data/lib/puma.rb +54 -0
  40. data/lib/puma/app/status.rb +68 -49
  41. data/lib/puma/binder.rb +191 -139
  42. data/lib/puma/cli.rb +15 -15
  43. data/lib/puma/client.rb +247 -226
  44. data/lib/puma/cluster.rb +221 -212
  45. data/lib/puma/cluster/worker.rb +183 -0
  46. data/lib/puma/cluster/worker_handle.rb +90 -0
  47. data/lib/puma/commonlogger.rb +2 -2
  48. data/lib/puma/configuration.rb +58 -51
  49. data/lib/puma/const.rb +32 -20
  50. data/lib/puma/control_cli.rb +109 -67
  51. data/lib/puma/detect.rb +24 -3
  52. data/lib/puma/dsl.rb +519 -121
  53. data/lib/puma/error_logger.rb +104 -0
  54. data/lib/puma/events.rb +55 -31
  55. data/lib/puma/io_buffer.rb +7 -5
  56. data/lib/puma/jruby_restart.rb +0 -58
  57. data/lib/puma/json.rb +96 -0
  58. data/lib/puma/launcher.rb +178 -68
  59. data/lib/puma/minissl.rb +147 -48
  60. data/lib/puma/minissl/context_builder.rb +79 -0
  61. data/lib/puma/null_io.rb +13 -1
  62. data/lib/puma/plugin.rb +6 -12
  63. data/lib/puma/plugin/tmp_restart.rb +2 -0
  64. data/lib/puma/queue_close.rb +26 -0
  65. data/lib/puma/rack/builder.rb +2 -4
  66. data/lib/puma/rack/urlmap.rb +2 -0
  67. data/lib/puma/rack_default.rb +2 -0
  68. data/lib/puma/reactor.rb +85 -316
  69. data/lib/puma/request.rb +467 -0
  70. data/lib/puma/runner.rb +31 -52
  71. data/lib/puma/server.rb +275 -726
  72. data/lib/puma/single.rb +11 -67
  73. data/lib/puma/state_file.rb +8 -3
  74. data/lib/puma/systemd.rb +46 -0
  75. data/lib/puma/thread_pool.rb +129 -81
  76. data/lib/puma/util.rb +13 -6
  77. data/lib/rack/handler/puma.rb +5 -6
  78. data/tools/Dockerfile +16 -0
  79. data/tools/trickletest.rb +0 -1
  80. metadata +45 -28
  81. data/ext/puma_http11/io_buffer.c +0 -155
  82. data/lib/puma/accept_nonblock.rb +0 -23
  83. data/lib/puma/compat.rb +0 -14
  84. data/lib/puma/convenient.rb +0 -25
  85. data/lib/puma/daemon_ext.rb +0 -33
  86. data/lib/puma/delegation.rb +0 -13
  87. data/lib/puma/java_io_buffer.rb +0 -47
  88. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/runner.rb CHANGED
@@ -14,10 +14,7 @@ module Puma
14
14
  @options = cli.options
15
15
  @app = nil
16
16
  @control = nil
17
- end
18
-
19
- def daemon?
20
- @options[:daemon]
17
+ @started_at = Time.now
21
18
  end
22
19
 
23
20
  def development?
@@ -32,7 +29,8 @@ module Puma
32
29
  @events.log str
33
30
  end
34
31
 
35
- def before_restart
32
+ # @version 5.0.0
33
+ def stop_control
36
34
  @control.stop(true) if @control
37
35
  end
38
36
 
@@ -50,36 +48,27 @@ module Puma
50
48
 
51
49
  require 'puma/app/status'
52
50
 
53
- uri = URI.parse str
54
-
55
- app = Puma::App::Status.new @launcher
56
-
57
51
  if token = @options[:control_auth_token]
58
- app.auth_token = token unless token.empty? or token == :none
52
+ token = nil if token.empty? || token == 'none'
59
53
  end
60
54
 
61
- control = Puma::Server.new app, @launcher.events
62
- control.min_threads = 0
63
- control.max_threads = 1
55
+ app = Puma::App::Status.new @launcher, token
64
56
 
65
- case uri.scheme
66
- when "tcp"
67
- log "* Starting control server on #{str}"
68
- control.add_tcp_listener uri.host, uri.port
69
- when "unix"
70
- log "* Starting control server on #{str}"
71
- path = "#{uri.host}#{uri.path}"
72
- mask = @options[:control_url_umask]
57
+ control = Puma::Server.new app, @launcher.events,
58
+ { min_threads: 0, max_threads: 1, queue_requests: false }
73
59
 
74
- control.add_unix_listener path, mask
75
- else
76
- error "Invalid control URI: #{str}"
77
- end
60
+ control.binder.parse [str], self, 'Starting control server'
78
61
 
79
- control.run
62
+ control.run thread_name: 'control'
80
63
  @control = control
81
64
  end
82
65
 
66
+ # @version 5.0.0
67
+ def close_control_listeners
68
+ @control.binder.close_listeners if @control
69
+ end
70
+
71
+ # @!attribute [r] ruby_engine
83
72
  def ruby_engine
84
73
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
85
74
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -97,12 +86,15 @@ module Puma
97
86
  max_t = @options[:max_threads]
98
87
 
99
88
  log "Puma starting in #{mode} mode..."
100
- log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
101
- log "* Min threads: #{min_t}, max threads: #{max_t}"
102
- log "* Environment: #{ENV['RACK_ENV']}"
89
+ log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
90
+ log "* Min threads: #{min_t}"
91
+ log "* Max threads: #{max_t}"
92
+ log "* Environment: #{ENV['RACK_ENV']}"
103
93
 
104
- if @options[:mode] == :tcp
105
- log "* Mode: Lopez Express (tcp)"
94
+ if mode == "cluster"
95
+ log "* Master PID: #{Process.pid}"
96
+ else
97
+ log "* PID: #{Process.pid}"
106
98
  end
107
99
  end
108
100
 
@@ -121,8 +113,8 @@ module Puma
121
113
  end
122
114
 
123
115
  STDOUT.reopen stdout, (append ? "a" : "w")
124
- STDOUT.sync = true
125
116
  STDOUT.puts "=== puma startup: #{Time.now} ==="
117
+ STDOUT.flush unless STDOUT.sync
126
118
  end
127
119
 
128
120
  if stderr
@@ -131,8 +123,13 @@ module Puma
131
123
  end
132
124
 
133
125
  STDERR.reopen stderr, (append ? "a" : "w")
134
- STDERR.sync = true
135
126
  STDERR.puts "=== puma startup: #{Time.now} ==="
127
+ STDERR.flush unless STDERR.sync
128
+ end
129
+
130
+ if @options[:mutate_stdout_and_stderr_to_sync_on_write]
131
+ STDOUT.sync = true
132
+ STDERR.sync = true
136
133
  end
137
134
  end
138
135
 
@@ -142,7 +139,6 @@ module Puma
142
139
  exit 1
143
140
  end
144
141
 
145
- # Load the app before we daemonize.
146
142
  begin
147
143
  @app = @launcher.config.app
148
144
  rescue Exception => e
@@ -153,31 +149,14 @@ module Puma
153
149
  @launcher.binder.parse @options[:binds], self
154
150
  end
155
151
 
152
+ # @!attribute [r] app
156
153
  def app
157
154
  @app ||= @launcher.config.app
158
155
  end
159
156
 
160
157
  def start_server
161
- min_t = @options[:min_threads]
162
- max_t = @options[:max_threads]
163
-
164
158
  server = Puma::Server.new app, @launcher.events, @options
165
- server.min_threads = min_t
166
- server.max_threads = max_t
167
159
  server.inherit_binder @launcher.binder
168
-
169
- if @options[:mode] == :tcp
170
- server.tcp_mode!
171
- end
172
-
173
- if @options[:early_hints]
174
- server.early_hints = true
175
- end
176
-
177
- unless development? || test?
178
- server.leak_stack_on_error = false
179
- end
180
-
181
160
  server
182
161
  end
183
162
  end
data/lib/puma/server.rb CHANGED
@@ -6,21 +6,15 @@ require 'puma/thread_pool'
6
6
  require 'puma/const'
7
7
  require 'puma/events'
8
8
  require 'puma/null_io'
9
- require 'puma/compat'
10
9
  require 'puma/reactor'
11
10
  require 'puma/client'
12
11
  require 'puma/binder'
13
- require 'puma/delegation'
14
- require 'puma/accept_nonblock'
15
12
  require 'puma/util'
16
-
17
- require 'puma/puma_http11'
18
-
19
- unless Puma.const_defined? "IOBuffer"
20
- require 'puma/io_buffer'
21
- end
13
+ require 'puma/io_buffer'
14
+ require 'puma/request'
22
15
 
23
16
  require 'socket'
17
+ require 'forwardable'
24
18
 
25
19
  module Puma
26
20
 
@@ -28,7 +22,7 @@ module Puma
28
22
  #
29
23
  # This class is used by the `Puma::Single` and `Puma::Cluster` classes
30
24
  # to generate one or more `Puma::Server` instances capable of handling requests.
31
- # Each Puma process will contain one `Puma::Server` instacne.
25
+ # Each Puma process will contain one `Puma::Server` instance.
32
26
  #
33
27
  # The `Puma::Server` instance pulls requests from the socket, adds them to a
34
28
  # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
@@ -37,18 +31,31 @@ module Puma
37
31
  class Server
38
32
 
39
33
  include Puma::Const
40
- extend Puma::Delegation
34
+ include Request
35
+ extend Forwardable
41
36
 
42
37
  attr_reader :thread
43
38
  attr_reader :events
39
+ attr_reader :min_threads, :max_threads # for #stats
40
+ attr_reader :requests_count # @version 5.0.0
41
+
42
+ # @todo the following may be deprecated in the future
43
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
44
+ :leak_stack_on_error,
45
+ :persistent_timeout, :reaping_time
46
+
47
+ # @deprecated v6.0.0
48
+ attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
49
+ :leak_stack_on_error, :min_threads, :max_threads,
50
+ :persistent_timeout, :reaping_time
51
+
44
52
  attr_accessor :app
53
+ attr_accessor :binder
54
+
55
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
56
+ :add_unix_listener, :connected_ports
45
57
 
46
- attr_accessor :min_threads
47
- attr_accessor :max_threads
48
- attr_accessor :persistent_timeout
49
- attr_accessor :auto_trim_time
50
- attr_accessor :reaping_time
51
- attr_accessor :first_data_timeout
58
+ ThreadLocalKey = :puma_server
52
59
 
53
60
  # Create a server for the rack app +app+.
54
61
  #
@@ -58,88 +65,115 @@ module Puma
58
65
  # Server#run returns a thread that you can join on to wait for the server
59
66
  # to do its work.
60
67
  #
68
+ # @note Several instance variables exist so they are available for testing,
69
+ # and have default values set via +fetch+. Normally the values are set via
70
+ # `::Puma::Configuration.puma_default_options`.
71
+ #
61
72
  def initialize(app, events=Events.stdio, options={})
62
73
  @app = app
63
74
  @events = events
64
75
 
65
- @check, @notify = Puma::Util.pipe
66
-
76
+ @check, @notify = nil
67
77
  @status = :stop
68
78
 
69
- @min_threads = 0
70
- @max_threads = 16
71
79
  @auto_trim_time = 30
72
80
  @reaping_time = 1
73
81
 
74
82
  @thread = nil
75
83
  @thread_pool = nil
76
- @early_hints = nil
77
84
 
78
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
79
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
85
+ @options = options
80
86
 
81
- @binder = Binder.new(events)
82
- @own_binder = true
87
+ @early_hints = options.fetch :early_hints, nil
88
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
89
+ @min_threads = options.fetch :min_threads, 0
90
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
91
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
92
+ @queue_requests = options.fetch :queue_requests, true
93
+ @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
94
+ @io_selector_backend = options.fetch :io_selector_backend, :auto
83
95
 
84
- @leak_stack_on_error = true
96
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
97
+ @leak_stack_on_error = @options[:environment] ? temp : true
85
98
 
86
- @options = options
87
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
99
+ @binder = Binder.new(events)
88
100
 
89
101
  ENV['RACK_ENV'] ||= "development"
90
102
 
91
103
  @mode = :http
92
104
 
93
105
  @precheck_closing = true
94
- end
95
106
 
96
- attr_accessor :binder, :leak_stack_on_error, :early_hints
97
-
98
- forward :add_tcp_listener, :@binder
99
- forward :add_ssl_listener, :@binder
100
- forward :add_unix_listener, :@binder
101
- forward :connected_port, :@binder
107
+ @requests_count = 0
108
+ end
102
109
 
103
110
  def inherit_binder(bind)
104
111
  @binder = bind
105
- @own_binder = false
106
112
  end
107
113
 
108
- def tcp_mode!
109
- @mode = :tcp
114
+ class << self
115
+ # @!attribute [r] current
116
+ def current
117
+ Thread.current[ThreadLocalKey]
118
+ end
119
+
120
+ # :nodoc:
121
+ # @version 5.0.0
122
+ def tcp_cork_supported?
123
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
124
+ end
125
+
126
+ # :nodoc:
127
+ # @version 5.0.0
128
+ def closed_socket_supported?
129
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
130
+ end
131
+ private :tcp_cork_supported?
132
+ private :closed_socket_supported?
110
133
  end
111
134
 
112
135
  # On Linux, use TCP_CORK to better control how the TCP stack
113
136
  # packetizes our stream. This improves both latency and throughput.
137
+ # socket parameter may be an MiniSSL::Socket, so use to_io
114
138
  #
115
- if RUBY_PLATFORM =~ /linux/
116
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
117
-
139
+ if tcp_cork_supported?
118
140
  # 6 == Socket::IPPROTO_TCP
119
141
  # 3 == TCP_CORK
120
142
  # 1/0 == turn on/off
121
143
  def cork_socket(socket)
144
+ skt = socket.to_io
122
145
  begin
123
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
146
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
124
147
  rescue IOError, SystemCallError
125
148
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
126
149
  end
127
150
  end
128
151
 
129
152
  def uncork_socket(socket)
153
+ skt = socket.to_io
130
154
  begin
131
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
155
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
132
156
  rescue IOError, SystemCallError
133
157
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
134
158
  end
135
159
  end
160
+ else
161
+ def cork_socket(socket)
162
+ end
163
+
164
+ def uncork_socket(socket)
165
+ end
166
+ end
167
+
168
+ if closed_socket_supported?
169
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
136
170
 
137
171
  def closed_socket?(socket)
138
- return false unless socket.kind_of? TCPSocket
139
- return false unless @precheck_closing
172
+ skt = socket.to_io
173
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
140
174
 
141
175
  begin
142
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
176
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
143
177
  rescue IOError, SystemCallError
144
178
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
145
179
  @precheck_closing = false
@@ -151,21 +185,17 @@ module Puma
151
185
  end
152
186
  end
153
187
  else
154
- def cork_socket(socket)
155
- end
156
-
157
- def uncork_socket(socket)
158
- end
159
-
160
188
  def closed_socket?(socket)
161
189
  false
162
190
  end
163
191
  end
164
192
 
193
+ # @!attribute [r] backlog
165
194
  def backlog
166
195
  @thread_pool and @thread_pool.backlog
167
196
  end
168
197
 
198
+ # @!attribute [r] running
169
199
  def running
170
200
  @thread_pool and @thread_pool.spawned
171
201
  end
@@ -178,172 +208,37 @@ module Puma
178
208
  # there are 5 threads sitting idle ready to take
179
209
  # a request. If one request comes in, then the
180
210
  # value would be 4 until it finishes processing.
211
+ # @!attribute [r] pool_capacity
181
212
  def pool_capacity
182
213
  @thread_pool and @thread_pool.pool_capacity
183
214
  end
184
215
 
185
- # Lopez Mode == raw tcp apps
186
-
187
- def run_lopez_mode(background=true)
188
- @thread_pool = ThreadPool.new(@min_threads,
189
- @max_threads,
190
- Hash) do |client, tl|
191
-
192
- io = client.to_io
193
- addr = io.peeraddr.last
194
-
195
- if addr.empty?
196
- # Set unix socket addrs to localhost
197
- addr = "127.0.0.1:0"
198
- else
199
- addr = "#{addr}:#{io.peeraddr[1]}"
200
- end
201
-
202
- env = { 'thread' => tl, REMOTE_ADDR => addr }
203
-
204
- begin
205
- @app.call env, client.to_io
206
- rescue Object => e
207
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
208
- STDERR.puts e.backtrace
209
- end
210
-
211
- client.close unless env['detach']
212
- end
213
-
214
- @events.fire :state, :running
215
-
216
- if background
217
- @thread = Thread.new { handle_servers_lopez_mode }
218
- return @thread
219
- else
220
- handle_servers_lopez_mode
221
- end
222
- end
223
-
224
- def handle_servers_lopez_mode
225
- begin
226
- check = @check
227
- sockets = [check] + @binder.ios
228
- pool = @thread_pool
229
-
230
- while @status == :run
231
- begin
232
- ios = IO.select sockets
233
- ios.first.each do |sock|
234
- if sock == check
235
- break if handle_check
236
- else
237
- begin
238
- if io = sock.accept_nonblock
239
- client = Client.new io, nil
240
- pool << client
241
- end
242
- rescue SystemCallError
243
- # nothing
244
- rescue Errno::ECONNABORTED
245
- # client closed the socket even before accept
246
- begin
247
- io.close
248
- rescue
249
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
250
- end
251
- end
252
- end
253
- end
254
- rescue Object => e
255
- @events.unknown_error self, e, "Listen loop"
256
- end
257
- end
258
-
259
- @events.fire :state, @status
260
-
261
- graceful_shutdown if @status == :stop || @status == :restart
262
-
263
- rescue Exception => e
264
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
265
- STDERR.puts e.backtrace
266
- ensure
267
- begin
268
- @check.close
269
- rescue
270
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
271
- end
272
-
273
- @notify.close
274
-
275
- if @status != :restart and @own_binder
276
- @binder.close
277
- end
278
- end
279
-
280
- @events.fire :state, :done
281
- end
282
216
  # Runs the server.
283
217
  #
284
218
  # If +background+ is true (the default) then a thread is spun
285
219
  # up in the background to handle requests. Otherwise requests
286
220
  # are handled synchronously.
287
221
  #
288
- def run(background=true)
222
+ def run(background=true, thread_name: 'server')
289
223
  BasicSocket.do_not_reverse_lookup = true
290
224
 
291
225
  @events.fire :state, :booting
292
226
 
293
227
  @status = :run
294
228
 
295
- if @mode == :tcp
296
- return run_lopez_mode(background)
297
- end
298
-
299
- queue_requests = @queue_requests
300
-
301
- @thread_pool = ThreadPool.new(@min_threads,
302
- @max_threads,
303
- IOBuffer) do |client, buffer|
304
-
305
- # Advertise this server into the thread
306
- Thread.current[ThreadLocalKey] = self
307
-
308
- process_now = false
309
-
310
- begin
311
- if queue_requests
312
- process_now = client.eagerly_finish
313
- else
314
- client.finish
315
- process_now = true
316
- end
317
- rescue MiniSSL::SSLError => e
318
- ssl_socket = client.io
319
- addr = ssl_socket.peeraddr.last
320
- cert = ssl_socket.peercert
321
-
322
- client.close
323
-
324
- @events.ssl_error self, addr, cert, e
325
- rescue HttpParserError => e
326
- client.write_400
327
- client.close
328
-
329
- @events.parse_error self, client.env, e
330
- rescue ConnectionError, EOFError
331
- client.close
332
- else
333
- if process_now
334
- process_client client, buffer
335
- else
336
- client.set_timeout @first_data_timeout
337
- @reactor.add client
338
- end
339
- end
340
- end
229
+ @thread_pool = ThreadPool.new(
230
+ @min_threads,
231
+ @max_threads,
232
+ ::Puma::IOBuffer,
233
+ &method(:process_client)
234
+ )
341
235
 
236
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
342
237
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
343
238
 
344
- if queue_requests
345
- @reactor = Reactor.new self, @thread_pool
346
- @reactor.run_in_thread
239
+ if @queue_requests
240
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
241
+ @reactor.run
347
242
  end
348
243
 
349
244
  if @reaping_time
@@ -354,22 +249,69 @@ module Puma
354
249
  @thread_pool.auto_trim!(@auto_trim_time)
355
250
  end
356
251
 
252
+ @check, @notify = Puma::Util.pipe unless @notify
253
+
357
254
  @events.fire :state, :running
358
255
 
359
256
  if background
360
- @thread = Thread.new { handle_servers }
257
+ @thread = Thread.new do
258
+ Puma.set_thread_name thread_name
259
+ handle_servers
260
+ end
361
261
  return @thread
362
262
  else
363
263
  handle_servers
364
264
  end
365
265
  end
366
266
 
267
+ # This method is called from the Reactor thread when a queued Client receives data,
268
+ # times out, or when the Reactor is shutting down.
269
+ #
270
+ # It is responsible for ensuring that a request has been completely received
271
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
272
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
273
+ # such as nginx) then the application would be subject to a slow client attack.
274
+ #
275
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
276
+ #
277
+ # The method checks to see if it has the full header and body with
278
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
279
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
280
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
281
+ # The Client is then removed from the reactor (return `true`).
282
+ #
283
+ # If a client object times out, a 408 response is written, its connection is closed,
284
+ # and the object is removed from the reactor (return `true`).
285
+ #
286
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
287
+ # ThreadPool, depending on their current state (#can_close?).
288
+ #
289
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
290
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
291
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
292
+ def reactor_wakeup(client)
293
+ shutdown = !@queue_requests
294
+ if client.try_to_finish || (shutdown && !client.can_close?)
295
+ @thread_pool << client
296
+ elsif shutdown || client.timeout == 0
297
+ client.timeout!
298
+ else
299
+ client.set_timeout(@first_data_timeout)
300
+ false
301
+ end
302
+ rescue StandardError => e
303
+ client_error(e, client)
304
+ client.close
305
+ true
306
+ end
307
+
367
308
  def handle_servers
368
309
  begin
369
310
  check = @check
370
311
  sockets = [check] + @binder.ios
371
312
  pool = @thread_pool
372
313
  queue_requests = @queue_requests
314
+ drain = @options[:drain_on_shutdown] ? 0 : nil
373
315
 
374
316
  remote_addr_value = nil
375
317
  remote_addr_header = nil
@@ -381,59 +323,58 @@ module Puma
381
323
  remote_addr_header = @options[:remote_address_header]
382
324
  end
383
325
 
384
- while @status == :run
326
+ while @status == :run || (drain && shutting_down?)
385
327
  begin
386
- ios = IO.select sockets
328
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
329
+ break unless ios
387
330
  ios.first.each do |sock|
388
331
  if sock == check
389
332
  break if handle_check
390
333
  else
391
- begin
392
- if io = sock.accept_nonblock
393
- client = Client.new io, @binder.env(sock)
394
- if remote_addr_value
395
- client.peerip = remote_addr_value
396
- elsif remote_addr_header
397
- client.remote_addr_header = remote_addr_header
398
- end
399
-
400
- pool << client
401
- pool.wait_until_not_full
402
- end
403
- rescue SystemCallError
404
- # nothing
405
- rescue Errno::ECONNABORTED
406
- # client closed the socket even before accept
407
- begin
408
- io.close
409
- rescue
410
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
411
- end
334
+ pool.wait_until_not_full
335
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
336
+
337
+ io = begin
338
+ sock.accept_nonblock
339
+ rescue IO::WaitReadable
340
+ next
412
341
  end
342
+ drain += 1 if shutting_down?
343
+ client = Client.new io, @binder.env(sock)
344
+ client.listener = sock
345
+ if remote_addr_value
346
+ client.peerip = remote_addr_value
347
+ elsif remote_addr_header
348
+ client.remote_addr_header = remote_addr_header
349
+ end
350
+ pool << client
413
351
  end
414
352
  end
415
353
  rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
354
+ @events.unknown_error e, nil, "Listen loop"
417
355
  end
418
356
  end
419
357
 
358
+ @events.debug "Drained #{drain} additional connections." if drain
420
359
  @events.fire :state, @status
421
360
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
361
  if queue_requests
424
- @reactor.clear!
362
+ @queue_requests = false
425
363
  @reactor.shutdown
426
364
  end
365
+ graceful_shutdown if @status == :stop || @status == :restart
427
366
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
367
+ @events.unknown_error e, nil, "Exception handling servers"
430
368
  ensure
431
- @check.close
432
- @notify.close
433
-
434
- if @status != :restart and @own_binder
435
- @binder.close
369
+ begin
370
+ @check.close unless @check.closed?
371
+ rescue Errno::EBADF, RuntimeError
372
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
373
+ # Errno::EBADF is infrequently raised
436
374
  end
375
+ @notify.close
376
+ @notify = nil
377
+ @check = nil
437
378
  end
438
379
 
439
380
  @events.fire :state, :done
@@ -458,86 +399,83 @@ module Puma
458
399
  return false
459
400
  end
460
401
 
461
- # Given a connection on +client+, handle the incoming requests.
402
+ # Given a connection on +client+, handle the incoming requests,
403
+ # or queue the connection in the Reactor if no request is available.
462
404
  #
463
- # This method support HTTP Keep-Alive so it may, depending on if the client
405
+ # This method is called from a ThreadPool worker thread.
406
+ #
407
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
464
408
  # indicates that it supports keep alive, wait for another request before
465
409
  # returning.
466
410
  #
411
+ # Return true if one or more requests were processed.
467
412
  def process_client(client, buffer)
413
+ # Advertise this server into the thread
414
+ Thread.current[ThreadLocalKey] = self
415
+
416
+ clean_thread_locals = @options[:clean_thread_locals]
417
+ close_socket = true
418
+
419
+ requests = 0
420
+
468
421
  begin
422
+ if @queue_requests &&
423
+ !client.eagerly_finish
469
424
 
470
- clean_thread_locals = @options[:clean_thread_locals]
471
- close_socket = true
425
+ client.set_timeout(@first_data_timeout)
426
+ if @reactor.add client
427
+ close_socket = false
428
+ return false
429
+ end
430
+ end
472
431
 
473
- requests = 0
432
+ with_force_shutdown(client) do
433
+ client.finish(@first_data_timeout)
434
+ end
474
435
 
475
436
  while true
476
- case handle_request(client, buffer)
437
+ @requests_count += 1
438
+ case handle_request(client, buffer, requests + 1)
477
439
  when false
478
- return
440
+ break
479
441
  when :async
480
442
  close_socket = false
481
- return
443
+ break
482
444
  when true
483
- return unless @queue_requests
484
445
  buffer.reset
485
446
 
486
447
  ThreadPool.clean_thread_locals if clean_thread_locals
487
448
 
488
449
  requests += 1
489
450
 
490
- check_for_more_data = @status == :run
451
+ # As an optimization, try to read the next request from the
452
+ # socket for a short time before returning to the reactor.
453
+ fast_check = @status == :run
454
+
455
+ # Always pass the client back to the reactor after a reasonable
456
+ # number of inline requests if there are other requests pending.
457
+ fast_check = false if requests >= @max_fast_inline &&
458
+ @thread_pool.backlog > 0
491
459
 
492
- if requests >= MAX_FAST_INLINE
493
- # This will mean that reset will only try to use the data it already
494
- # has buffered and won't try to read more data. What this means is that
495
- # every client, independent of their request speed, gets treated like a slow
496
- # one once every MAX_FAST_INLINE requests.
497
- check_for_more_data = false
460
+ next_request_ready = with_force_shutdown(client) do
461
+ client.reset(fast_check)
498
462
  end
499
463
 
500
- unless client.reset(check_for_more_data)
501
- close_socket = false
464
+ unless next_request_ready
465
+ break unless @queue_requests
502
466
  client.set_timeout @persistent_timeout
503
- @reactor.add client
504
- return
467
+ if @reactor.add client
468
+ close_socket = false
469
+ break
470
+ end
505
471
  end
506
472
  end
507
473
  end
508
-
509
- # The client disconnected while we were reading data
510
- rescue ConnectionError
511
- # Swallow them. The ensure tries to close +client+ down
512
-
513
- # SSL handshake error
514
- rescue MiniSSL::SSLError => e
515
- lowlevel_error(e, client.env)
516
-
517
- ssl_socket = client.io
518
- addr = ssl_socket.peeraddr.last
519
- cert = ssl_socket.peercert
520
-
521
- close_socket = true
522
-
523
- @events.ssl_error self, addr, cert, e
524
-
525
- # The client doesn't know HTTP well
526
- rescue HttpParserError => e
527
- lowlevel_error(e, client.env)
528
-
529
- client.write_400
530
-
531
- @events.parse_error self, client.env, e
532
-
533
- # Server error
474
+ true
534
475
  rescue StandardError => e
535
- lowlevel_error(e, client.env)
536
-
537
- client.write_500
538
-
539
- @events.unknown_error self, e, "Read"
540
-
476
+ client_error(e, client)
477
+ # The ensure tries to close +client+ down
478
+ requests > 0
541
479
  ensure
542
480
  buffer.reset
543
481
 
@@ -547,406 +485,57 @@ module Puma
547
485
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
548
486
  # Already closed
549
487
  rescue StandardError => e
550
- @events.unknown_error self, e, "Client"
488
+ @events.unknown_error e, nil, "Client"
551
489
  end
552
490
  end
553
491
  end
554
492
 
555
- # Given a Hash +env+ for the request read from +client+, add
556
- # and fixup keys to comply with Rack's env guidelines.
557
- #
558
- def normalize_env(env, client)
559
- if host = env[HTTP_HOST]
560
- if colon = host.index(":")
561
- env[SERVER_NAME] = host[0, colon]
562
- env[SERVER_PORT] = host[colon+1, host.bytesize]
563
- else
564
- env[SERVER_NAME] = host
565
- env[SERVER_PORT] = default_server_port(env)
566
- end
567
- else
568
- env[SERVER_NAME] = LOCALHOST
569
- env[SERVER_PORT] = default_server_port(env)
570
- end
571
-
572
- unless env[REQUEST_PATH]
573
- # it might be a dumbass full host request header
574
- uri = URI.parse(env[REQUEST_URI])
575
- env[REQUEST_PATH] = uri.path
576
-
577
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
578
-
579
- # A nil env value will cause a LintError (and fatal errors elsewhere),
580
- # so only set the env value if there actually is a value.
581
- env[QUERY_STRING] = uri.query if uri.query
582
- end
583
-
584
- env[PATH_INFO] = env[REQUEST_PATH]
585
-
586
- # From http://www.ietf.org/rfc/rfc3875 :
587
- # "Script authors should be aware that the REMOTE_ADDR and
588
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
589
- # may not identify the ultimate source of the request.
590
- # They identify the client for the immediate request to the
591
- # server; that client may be a proxy, gateway, or other
592
- # intermediary acting on behalf of the actual source client."
593
- #
594
-
595
- unless env.key?(REMOTE_ADDR)
596
- begin
597
- addr = client.peerip
598
- rescue Errno::ENOTCONN
599
- # Client disconnects can result in an inability to get the
600
- # peeraddr from the socket; default to localhost.
601
- addr = LOCALHOST_IP
602
- end
603
-
604
- # Set unix socket addrs to localhost
605
- addr = LOCALHOST_IP if addr.empty?
606
-
607
- env[REMOTE_ADDR] = addr
608
- end
609
- end
610
-
611
- def default_server_port(env)
612
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
613
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
614
- end
615
-
616
- # Given the request +env+ from +client+ and a partial request body
617
- # in +body+, finish reading the body if there is one and invoke
618
- # the rack app. Then construct the response and write it back to
619
- # +client+
620
- #
621
- # +cl+ is the previously fetched Content-Length header if there
622
- # was one. This is an optimization to keep from having to look
623
- # it up again.
624
- #
625
- def handle_request(req, lines)
626
- env = req.env
627
- client = req.io
628
-
629
- return false if closed_socket?(client)
630
-
631
- normalize_env env, req
632
-
633
- env[PUMA_SOCKET] = client
634
-
635
- if env[HTTPS_KEY] && client.peercert
636
- env[PUMA_PEERCERT] = client.peercert
637
- end
638
-
639
- env[HIJACK_P] = true
640
- env[HIJACK] = req
641
-
642
- body = req.body
643
-
644
- head = env[REQUEST_METHOD] == HEAD
645
-
646
- env[RACK_INPUT] = body
647
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
648
-
649
- if @early_hints
650
- env[EARLY_HINTS] = lambda { |headers|
651
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
652
-
653
- headers.each_pair do |k, vs|
654
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
655
- vs.to_s.split(NEWLINE).each do |v|
656
- next if possible_header_injection?(v)
657
- fast_write client, "#{k}: #{v}\r\n"
658
- end
659
- else
660
- fast_write client, "#{k}: #{vs}\r\n"
661
- end
662
- end
663
-
664
- fast_write client, "\r\n".freeze
665
- }
666
- end
667
-
668
- # Fixup any headers with , in the name to have _ now. We emit
669
- # headers with , in them during the parse phase to avoid ambiguity
670
- # with the - to _ conversion for critical headers. But here for
671
- # compatibility, we'll convert them back. This code is written to
672
- # avoid allocation in the common case (ie there are no headers
673
- # with , in their names), that's why it has the extra conditionals.
674
-
675
- to_delete = nil
676
- to_add = nil
677
-
678
- env.each do |k,v|
679
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
680
- if to_delete
681
- to_delete << k
682
- else
683
- to_delete = [k]
684
- end
685
-
686
- unless to_add
687
- to_add = {}
688
- end
689
-
690
- to_add[k.gsub(",", "_")] = v
691
- end
692
- end
693
-
694
- if to_delete
695
- to_delete.each { |k| env.delete(k) }
696
- env.merge! to_add
697
- end
698
-
699
- # A rack extension. If the app writes #call'ables to this
700
- # array, we will invoke them when the request is done.
701
- #
702
- after_reply = env[RACK_AFTER_REPLY] = []
703
-
704
- begin
705
- begin
706
- status, headers, res_body = @app.call(env)
707
-
708
- return :async if req.hijacked
709
-
710
- status = status.to_i
711
-
712
- if status == -1
713
- unless headers.empty? and res_body == []
714
- raise "async response must have empty headers and body"
715
- end
716
-
717
- return :async
718
- end
719
- rescue ThreadPool::ForceShutdown => e
720
- @events.log "Detected force shutdown of a thread, returning 503"
721
- @events.unknown_error self, e, "Rack app"
722
-
723
- status = 503
724
- headers = {}
725
- res_body = ["Request was internally terminated early\n"]
726
-
727
- rescue Exception => e
728
- @events.unknown_error self, e, "Rack app", env
729
-
730
- status, headers, res_body = lowlevel_error(e, env)
731
- end
732
-
733
- content_length = nil
734
- no_body = head
735
-
736
- if res_body.kind_of? Array and res_body.size == 1
737
- content_length = res_body[0].bytesize
738
- end
739
-
740
- cork_socket client
741
-
742
- line_ending = LINE_END
743
- colon = COLON
744
-
745
- http_11 = if env[HTTP_VERSION] == HTTP_11
746
- allow_chunked = true
747
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
748
- include_keepalive_header = false
749
-
750
- # An optimization. The most common response is 200, so we can
751
- # reply with the proper 200 status without having to compute
752
- # the response header.
753
- #
754
- if status == 200
755
- lines << HTTP_11_200
756
- else
757
- lines.append "HTTP/1.1 ", status.to_s, " ",
758
- fetch_status_code(status), line_ending
759
-
760
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
761
- end
762
- true
763
- else
764
- allow_chunked = false
765
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
766
- include_keepalive_header = keep_alive
767
-
768
- # Same optimization as above for HTTP/1.1
769
- #
770
- if status == 200
771
- lines << HTTP_10_200
772
- else
773
- lines.append "HTTP/1.0 ", status.to_s, " ",
774
- fetch_status_code(status), line_ending
775
-
776
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
777
- end
778
- false
779
- end
780
-
781
- response_hijack = nil
782
-
783
- headers.each do |k, vs|
784
- case k.downcase
785
- when CONTENT_LENGTH2
786
- next if possible_header_injection?(vs)
787
- content_length = vs
788
- next
789
- when TRANSFER_ENCODING
790
- allow_chunked = false
791
- content_length = nil
792
- when HIJACK
793
- response_hijack = vs
794
- next
795
- end
796
-
797
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
798
- vs.to_s.split(NEWLINE).each do |v|
799
- next if possible_header_injection?(v)
800
- lines.append k, colon, v, line_ending
801
- end
802
- else
803
- lines.append k, colon, line_ending
804
- end
805
- end
806
-
807
- if include_keepalive_header
808
- lines << CONNECTION_KEEP_ALIVE
809
- elsif http_11 && !keep_alive
810
- lines << CONNECTION_CLOSE
811
- end
812
-
813
- if no_body
814
- if content_length and status != 204
815
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
816
- end
817
-
818
- lines << line_ending
819
- fast_write client, lines.to_s
820
- return keep_alive
821
- end
822
-
823
- if content_length
824
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
825
- chunked = false
826
- elsif !response_hijack and allow_chunked
827
- lines << TRANSFER_ENCODING_CHUNKED
828
- chunked = true
829
- end
830
-
831
- lines << line_ending
832
-
833
- fast_write client, lines.to_s
834
-
835
- if response_hijack
836
- response_hijack.call client
837
- return :async
838
- end
839
-
840
- begin
841
- res_body.each do |part|
842
- next if part.bytesize.zero?
843
- if chunked
844
- fast_write client, part.bytesize.to_s(16)
845
- fast_write client, line_ending
846
- fast_write client, part
847
- fast_write client, line_ending
848
- else
849
- fast_write client, part
850
- end
851
-
852
- client.flush
853
- end
854
-
855
- if chunked
856
- fast_write client, CLOSE_CHUNKED
857
- client.flush
858
- end
859
- rescue SystemCallError, IOError
860
- raise ConnectionError, "Connection error detected during write"
861
- end
862
-
863
- ensure
864
- uncork_socket client
865
-
866
- body.close
867
- req.tempfile.unlink if req.tempfile
868
- res_body.close if res_body.respond_to? :close
869
-
870
- after_reply.each { |o| o.call }
871
- end
872
-
873
- return keep_alive
874
- end
875
-
876
- def fetch_status_code(status)
877
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
493
+ # Triggers a client timeout if the thread-pool shuts down
494
+ # during execution of the provided block.
495
+ def with_force_shutdown(client, &block)
496
+ @thread_pool.with_force_shutdown(&block)
497
+ rescue ThreadPool::ForceShutdown
498
+ client.timeout!
878
499
  end
879
- private :fetch_status_code
880
500
 
881
- # Given the request +env+ from +client+ and the partial body +body+
882
- # plus a potential Content-Length value +cl+, finish reading
883
- # the body and return it.
884
- #
885
- # If the body is larger than MAX_BODY, a Tempfile object is used
886
- # for the body, otherwise a StringIO is used.
887
- #
888
- def read_body(env, client, body, cl)
889
- content_length = cl.to_i
501
+ # :nocov:
890
502
 
891
- remain = content_length - body.bytesize
503
+ # Handle various error types thrown by Client I/O operations.
504
+ def client_error(e, client)
505
+ # Swallow, do not log
506
+ return if [ConnectionError, EOFError].include?(e.class)
892
507
 
893
- return StringIO.new(body) if remain <= 0
894
-
895
- # Use a Tempfile if there is a lot of data left
896
- if remain > MAX_BODY
897
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
898
- stream.binmode
508
+ lowlevel_error(e, client.env)
509
+ case e
510
+ when MiniSSL::SSLError
511
+ @events.ssl_error e, client.io
512
+ when HttpParserError
513
+ client.write_error(400)
514
+ @events.parse_error e, client
899
515
  else
900
- # The body[0,0] trick is to get an empty string in the same
901
- # encoding as body.
902
- stream = StringIO.new body[0,0]
903
- end
904
-
905
- stream.write body
906
-
907
- # Read an odd sized chunk so we can read even sized ones
908
- # after this
909
- chunk = client.readpartial(remain % CHUNK_SIZE)
910
-
911
- # No chunk means a closed socket
912
- unless chunk
913
- stream.close
914
- return nil
915
- end
916
-
917
- remain -= stream.write(chunk)
918
-
919
- # Raed the rest of the chunks
920
- while remain > 0
921
- chunk = client.readpartial(CHUNK_SIZE)
922
- unless chunk
923
- stream.close
924
- return nil
925
- end
926
-
927
- remain -= stream.write(chunk)
516
+ client.write_error(500)
517
+ @events.unknown_error e, nil, "Read"
928
518
  end
929
-
930
- stream.rewind
931
-
932
- return stream
933
519
  end
934
520
 
935
521
  # A fallback rack response if +@app+ raises as exception.
936
522
  #
937
- def lowlevel_error(e, env)
523
+ def lowlevel_error(e, env, status=500)
938
524
  if handler = @options[:lowlevel_error_handler]
939
525
  if handler.arity == 1
940
526
  return handler.call(e)
941
- else
527
+ elsif handler.arity == 2
942
528
  return handler.call(e, env)
529
+ else
530
+ return handler.call(e, env, status)
943
531
  end
944
532
  end
945
533
 
946
534
  if @leak_stack_on_error
947
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
535
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
536
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
948
537
  else
949
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
538
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
950
539
  end
951
540
  end
952
541
 
@@ -968,31 +557,13 @@ module Puma
968
557
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
969
558
  end
970
559
 
971
- if @options[:drain_on_shutdown]
972
- count = 0
973
-
974
- while true
975
- ios = IO.select @binder.ios, nil, nil, 0
976
- break unless ios
977
-
978
- ios.first.each do |sock|
979
- begin
980
- if io = sock.accept_nonblock
981
- count += 1
982
- client = Client.new io, @binder.env(sock)
983
- @thread_pool << client
984
- end
985
- rescue SystemCallError
986
- end
987
- end
988
- end
989
-
990
- @events.debug "Drained #{count} additional connections."
560
+ if @status != :restart
561
+ @binder.close
991
562
  end
992
563
 
993
564
  if @thread_pool
994
565
  if timeout = @options[:force_shutdown_after]
995
- @thread_pool.shutdown timeout.to_i
566
+ @thread_pool.shutdown timeout.to_f
996
567
  else
997
568
  @thread_pool.shutdown
998
569
  end
@@ -1000,18 +571,16 @@ module Puma
1000
571
  end
1001
572
 
1002
573
  def notify_safely(message)
1003
- begin
1004
- @notify << message
1005
- rescue IOError
1006
- # The server, in another thread, is shutting down
574
+ @notify << message
575
+ rescue IOError, NoMethodError, Errno::EPIPE
576
+ # The server, in another thread, is shutting down
577
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
578
+ rescue RuntimeError => e
579
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
580
+ if e.message.include?('IOError')
1007
581
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1008
- rescue RuntimeError => e
1009
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
1010
- if e.message.include?('IOError')
1011
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1012
- else
1013
- raise e
1014
- end
582
+ else
583
+ raise e
1015
584
  end
1016
585
  end
1017
586
  private :notify_safely
@@ -1029,44 +598,24 @@ module Puma
1029
598
  @thread.join if @thread && sync
1030
599
  end
1031
600
 
1032
- def begin_restart
601
+ def begin_restart(sync=false)
1033
602
  notify_safely(RESTART_COMMAND)
1034
- end
1035
-
1036
- def fast_write(io, str)
1037
- n = 0
1038
- while true
1039
- begin
1040
- n = io.syswrite str
1041
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
1042
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
1043
- raise ConnectionError, "Socket timeout writing data"
1044
- end
1045
-
1046
- retry
1047
- rescue Errno::EPIPE, SystemCallError, IOError
1048
- raise ConnectionError, "Socket timeout writing data"
1049
- end
1050
-
1051
- return if n == str.bytesize
1052
- str = str.byteslice(n..-1)
1053
- end
1054
- end
1055
- private :fast_write
1056
-
1057
- ThreadLocalKey = :puma_server
1058
-
1059
- def self.current
1060
- Thread.current[ThreadLocalKey]
603
+ @thread.join if @thread && sync
1061
604
  end
1062
605
 
1063
606
  def shutting_down?
1064
607
  @status == :stop || @status == :restart
1065
608
  end
1066
609
 
1067
- def possible_header_injection?(header_value)
1068
- HTTP_INJECTION_REGEX =~ header_value.to_s
610
+ # List of methods invoked by #stats.
611
+ # @version 5.0.0
612
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
613
+
614
+ # Returns a hash of stats about the running server for reporting purposes.
615
+ # @version 5.0.0
616
+ # @!attribute [r] stats
617
+ def stats
618
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1069
619
  end
1070
- private :possible_header_injection?
1071
620
  end
1072
621
  end