puma 4.3.6 → 5.3.2

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