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