puma 4.3.3 → 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 +1348 -519
  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 +48 -48
  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 +4 -2
  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 +32 -50
  35. data/lib/puma.rb +46 -0
  36. data/lib/puma/app/status.rb +48 -35
  37. data/lib/puma/binder.rb +177 -103
  38. data/lib/puma/cli.rb +11 -15
  39. data/lib/puma/client.rb +83 -76
  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 -698
  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 +28 -24
  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.
404
+ #
405
+ # This method is called from a ThreadPool worker thread.
458
406
  #
459
- # This method support HTTP Keep-Alive so it may, depending on if the client
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
487
454
 
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
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
459
+
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,386 +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"
488
+ @events.unknown_error e, nil, "Client"
547
489
  end
548
490
  end
549
491
  end
550
492
 
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
598
- end
599
-
600
- # Set unix socket addrs to localhost
601
- addr = LOCALHOST_IP if addr.empty?
602
-
603
- env[REMOTE_ADDR] = addr
604
- end
605
- end
606
-
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
613
- end
614
-
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
635
-
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
- # A rack extension. If the app writes #call'ables to this
676
- # array, we will invoke them when the request is done.
677
- #
678
- after_reply = env[RACK_AFTER_REPLY] = []
679
-
680
- begin
681
- begin
682
- status, headers, res_body = @app.call(env)
683
-
684
- return :async if req.hijacked
685
-
686
- status = status.to_i
687
-
688
- if status == -1
689
- unless headers.empty? and res_body == []
690
- raise "async response must have empty headers and body"
691
- end
692
-
693
- return :async
694
- end
695
- rescue ThreadPool::ForceShutdown => e
696
- @events.log "Detected force shutdown of a thread, returning 503"
697
- @events.unknown_error self, e, "Rack app"
698
-
699
- status = 503
700
- headers = {}
701
- res_body = ["Request was internally terminated early\n"]
702
-
703
- rescue Exception => e
704
- @events.unknown_error self, e, "Rack app", env
705
-
706
- status, headers, res_body = lowlevel_error(e, env)
707
- end
708
-
709
- content_length = nil
710
- no_body = head
711
-
712
- if res_body.kind_of? Array and res_body.size == 1
713
- content_length = res_body[0].bytesize
714
- end
715
-
716
- cork_socket client
717
-
718
- line_ending = LINE_END
719
- colon = COLON
720
-
721
- http_11 = if env[HTTP_VERSION] == HTTP_11
722
- allow_chunked = true
723
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
724
- include_keepalive_header = false
725
-
726
- # An optimization. The most common response is 200, so we can
727
- # reply with the proper 200 status without having to compute
728
- # the response header.
729
- #
730
- if status == 200
731
- lines << HTTP_11_200
732
- else
733
- lines.append "HTTP/1.1 ", status.to_s, " ",
734
- fetch_status_code(status), line_ending
735
-
736
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
737
- end
738
- true
739
- else
740
- allow_chunked = false
741
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
742
- include_keepalive_header = keep_alive
743
-
744
- # Same optimization as above for HTTP/1.1
745
- #
746
- if status == 200
747
- lines << HTTP_10_200
748
- else
749
- lines.append "HTTP/1.0 ", status.to_s, " ",
750
- fetch_status_code(status), line_ending
751
-
752
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
753
- end
754
- false
755
- end
756
-
757
- response_hijack = nil
758
-
759
- headers.each do |k, vs|
760
- case k.downcase
761
- when CONTENT_LENGTH2
762
- next if possible_header_injection?(vs)
763
- content_length = vs
764
- next
765
- when TRANSFER_ENCODING
766
- allow_chunked = false
767
- content_length = nil
768
- when HIJACK
769
- response_hijack = vs
770
- next
771
- end
772
-
773
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
774
- vs.to_s.split(NEWLINE).each do |v|
775
- next if possible_header_injection?(v)
776
- lines.append k, colon, v, line_ending
777
- end
778
- else
779
- lines.append k, colon, line_ending
780
- end
781
- end
782
-
783
- if include_keepalive_header
784
- lines << CONNECTION_KEEP_ALIVE
785
- elsif http_11 && !keep_alive
786
- lines << CONNECTION_CLOSE
787
- end
788
-
789
- if no_body
790
- if content_length and status != 204
791
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
792
- end
793
-
794
- lines << line_ending
795
- fast_write client, lines.to_s
796
- return keep_alive
797
- end
798
-
799
- if content_length
800
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
801
- chunked = false
802
- elsif !response_hijack and allow_chunked
803
- lines << TRANSFER_ENCODING_CHUNKED
804
- chunked = true
805
- end
806
-
807
- lines << line_ending
808
-
809
- fast_write client, lines.to_s
810
-
811
- if response_hijack
812
- response_hijack.call client
813
- return :async
814
- end
815
-
816
- begin
817
- res_body.each do |part|
818
- next if part.bytesize.zero?
819
- if chunked
820
- fast_write client, part.bytesize.to_s(16)
821
- fast_write client, line_ending
822
- fast_write client, part
823
- fast_write client, line_ending
824
- else
825
- fast_write client, part
826
- end
827
-
828
- client.flush
829
- end
830
-
831
- if chunked
832
- fast_write client, CLOSE_CHUNKED
833
- client.flush
834
- end
835
- rescue SystemCallError, IOError
836
- raise ConnectionError, "Connection error detected during write"
837
- end
838
-
839
- ensure
840
- uncork_socket client
841
-
842
- body.close
843
- req.tempfile.unlink if req.tempfile
844
- res_body.close if res_body.respond_to? :close
845
-
846
- after_reply.each { |o| o.call }
847
- end
848
-
849
- return keep_alive
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!
850
499
  end
851
500
 
852
- def fetch_status_code(status)
853
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
854
- end
855
- private :fetch_status_code
501
+ # :nocov:
856
502
 
857
- # Given the request +env+ from +client+ and the partial body +body+
858
- # plus a potential Content-Length value +cl+, finish reading
859
- # the body and return it.
860
- #
861
- # If the body is larger than MAX_BODY, a Tempfile object is used
862
- # for the body, otherwise a StringIO is used.
863
- #
864
- def read_body(env, client, body, cl)
865
- content_length = cl.to_i
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)
866
507
 
867
- remain = content_length - body.bytesize
868
-
869
- return StringIO.new(body) if remain <= 0
870
-
871
- # Use a Tempfile if there is a lot of data left
872
- if remain > MAX_BODY
873
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
874
- 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
875
515
  else
876
- # The body[0,0] trick is to get an empty string in the same
877
- # encoding as body.
878
- stream = StringIO.new body[0,0]
879
- end
880
-
881
- stream.write body
882
-
883
- # Read an odd sized chunk so we can read even sized ones
884
- # after this
885
- chunk = client.readpartial(remain % CHUNK_SIZE)
886
-
887
- # No chunk means a closed socket
888
- unless chunk
889
- stream.close
890
- return nil
891
- end
892
-
893
- remain -= stream.write(chunk)
894
-
895
- # Raed the rest of the chunks
896
- while remain > 0
897
- chunk = client.readpartial(CHUNK_SIZE)
898
- unless chunk
899
- stream.close
900
- return nil
901
- end
902
-
903
- remain -= stream.write(chunk)
516
+ client.write_error(500)
517
+ @events.unknown_error e, nil, "Read"
904
518
  end
905
-
906
- stream.rewind
907
-
908
- return stream
909
519
  end
910
520
 
911
521
  # A fallback rack response if +@app+ raises as exception.
912
522
  #
913
- def lowlevel_error(e, env)
523
+ def lowlevel_error(e, env, status=500)
914
524
  if handler = @options[:lowlevel_error_handler]
915
525
  if handler.arity == 1
916
526
  return handler.call(e)
917
- else
527
+ elsif handler.arity == 2
918
528
  return handler.call(e, env)
529
+ else
530
+ return handler.call(e, env, status)
919
531
  end
920
532
  end
921
533
 
922
534
  if @leak_stack_on_error
923
- [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}"]]
924
537
  else
925
- [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"]]
926
539
  end
927
540
  end
928
541
 
@@ -944,35 +557,13 @@ module Puma
944
557
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
945
558
  end
946
559
 
947
- if @options[:drain_on_shutdown]
948
- count = 0
949
-
950
- while true
951
- ios = IO.select @binder.ios, nil, nil, 0
952
- break unless ios
953
-
954
- ios.first.each do |sock|
955
- begin
956
- if io = sock.accept_nonblock
957
- count += 1
958
- client = Client.new io, @binder.env(sock)
959
- @thread_pool << client
960
- end
961
- rescue SystemCallError
962
- end
963
- end
964
- end
965
-
966
- @events.debug "Drained #{count} additional connections."
967
- end
968
-
969
560
  if @status != :restart
970
561
  @binder.close
971
562
  end
972
563
 
973
564
  if @thread_pool
974
565
  if timeout = @options[:force_shutdown_after]
975
- @thread_pool.shutdown timeout.to_i
566
+ @thread_pool.shutdown timeout.to_f
976
567
  else
977
568
  @thread_pool.shutdown
978
569
  end
@@ -980,18 +571,16 @@ module Puma
980
571
  end
981
572
 
982
573
  def notify_safely(message)
983
- begin
984
- @notify << message
985
- rescue IOError
986
- # 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')
987
581
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
988
- rescue RuntimeError => e
989
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
990
- if e.message.include?('IOError')
991
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
992
- else
993
- raise e
994
- end
582
+ else
583
+ raise e
995
584
  end
996
585
  end
997
586
  private :notify_safely
@@ -1009,44 +598,24 @@ module Puma
1009
598
  @thread.join if @thread && sync
1010
599
  end
1011
600
 
1012
- def begin_restart
601
+ def begin_restart(sync=false)
1013
602
  notify_safely(RESTART_COMMAND)
1014
- end
1015
-
1016
- def fast_write(io, str)
1017
- n = 0
1018
- while true
1019
- begin
1020
- n = io.syswrite str
1021
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
1022
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
1023
- raise ConnectionError, "Socket timeout writing data"
1024
- end
1025
-
1026
- retry
1027
- rescue Errno::EPIPE, SystemCallError, IOError
1028
- raise ConnectionError, "Socket timeout writing data"
1029
- end
1030
-
1031
- return if n == str.bytesize
1032
- str = str.byteslice(n..-1)
1033
- end
1034
- end
1035
- private :fast_write
1036
-
1037
- ThreadLocalKey = :puma_server
1038
-
1039
- def self.current
1040
- Thread.current[ThreadLocalKey]
603
+ @thread.join if @thread && sync
1041
604
  end
1042
605
 
1043
606
  def shutting_down?
1044
607
  @status == :stop || @status == :restart
1045
608
  end
1046
609
 
1047
- def possible_header_injection?(header_value)
1048
- 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
1049
619
  end
1050
- private :possible_header_injection?
1051
620
  end
1052
621
  end