puma 4.3.4-java → 5.0.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +87 -8
  3. data/LICENSE +23 -20
  4. data/README.md +26 -13
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/signals.md +7 -6
  16. data/docs/systemd.md +1 -63
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  18. data/ext/puma_http11/extconf.rb +4 -3
  19. data/ext/puma_http11/http11_parser.c +3 -1
  20. data/ext/puma_http11/http11_parser.rl +3 -1
  21. data/ext/puma_http11/mini_ssl.c +15 -2
  22. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  23. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  24. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  25. data/ext/puma_http11/puma_http11.c +7 -38
  26. data/lib/puma.rb +17 -0
  27. data/lib/puma/app/status.rb +18 -3
  28. data/lib/puma/binder.rb +88 -68
  29. data/lib/puma/cli.rb +7 -15
  30. data/lib/puma/client.rb +57 -12
  31. data/lib/puma/cluster.rb +191 -74
  32. data/lib/puma/commonlogger.rb +2 -2
  33. data/lib/puma/configuration.rb +31 -42
  34. data/lib/puma/const.rb +3 -3
  35. data/lib/puma/control_cli.rb +29 -17
  36. data/lib/puma/detect.rb +17 -0
  37. data/lib/puma/dsl.rb +144 -70
  38. data/lib/puma/error_logger.rb +97 -0
  39. data/lib/puma/events.rb +35 -31
  40. data/lib/puma/io_buffer.rb +9 -2
  41. data/lib/puma/jruby_restart.rb +0 -58
  42. data/lib/puma/launcher.rb +49 -31
  43. data/lib/puma/minissl.rb +60 -18
  44. data/lib/puma/minissl/context_builder.rb +0 -3
  45. data/lib/puma/null_io.rb +1 -1
  46. data/lib/puma/plugin.rb +1 -10
  47. data/lib/puma/puma_http11.jar +0 -0
  48. data/lib/puma/rack/builder.rb +0 -4
  49. data/lib/puma/reactor.rb +9 -4
  50. data/lib/puma/runner.rb +8 -36
  51. data/lib/puma/server.rb +141 -186
  52. data/lib/puma/single.rb +7 -64
  53. data/lib/puma/state_file.rb +6 -3
  54. data/lib/puma/thread_pool.rb +94 -49
  55. data/lib/rack/handler/puma.rb +1 -3
  56. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  57. metadata +17 -19
  58. data/docs/tcp_mode.md +0 -96
  59. data/ext/puma_http11/io_buffer.c +0 -155
  60. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  61. data/lib/puma/tcp_logger.rb +0 -41
  62. data/tools/jungle/README.md +0 -19
  63. data/tools/jungle/init.d/README.md +0 -61
  64. data/tools/jungle/init.d/puma +0 -421
  65. data/tools/jungle/init.d/run-puma +0 -18
@@ -5,8 +5,18 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
+ # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
+ require 'puma/puma_http11'
10
+
8
11
  module Puma
9
12
  module MiniSSL
13
+ # Define constant at runtime, as it's easy to determine at built time,
14
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
15
+ # @version 5.0.0
16
+ HAS_TLS1_3 = !IS_JRUBY &&
17
+ (OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
18
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
19
+
10
20
  class Socket
11
21
  def initialize(socket, engine)
12
22
  @socket = socket
@@ -22,6 +32,26 @@ module Puma
22
32
  @socket.closed?
23
33
  end
24
34
 
35
+ # Returns a two element array,
36
+ # first is protocol version (SSL_get_version),
37
+ # second is 'handshake' state (SSL_state_string)
38
+ #
39
+ # Used for dropping tcp connections to ssl.
40
+ # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
41
+ # @version 5.0.0
42
+ #
43
+ def ssl_version_state
44
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
45
+ end
46
+
47
+ # Used to check the handshake status, in particular when a TCP connection
48
+ # is made with TLSv1.3 as an available protocol
49
+ # @version 5.0.0
50
+ def bad_tlsv1_3?
51
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
52
+ end
53
+ private :bad_tlsv1_3?
54
+
25
55
  def readpartial(size)
26
56
  while true
27
57
  output = @engine.read
@@ -41,6 +71,7 @@ module Puma
41
71
 
42
72
  def engine_read_all
43
73
  output = @engine.read
74
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
75
  while output and additional_output = @engine.read
45
76
  output << additional_output
46
77
  end
@@ -107,14 +138,18 @@ module Puma
107
138
  alias_method :<<, :write
108
139
 
109
140
  # This is a temporary fix to deal with websockets code using
110
- # write_nonblock. The problem with implementing it properly
141
+ # write_nonblock.
142
+
143
+ # The problem with implementing it properly
111
144
  # is that it means we'd have to have the ability to rewind
112
145
  # an engine because after we write+extract, the socket
113
146
  # write_nonblock call might raise an exception and later
114
147
  # code would pass the same data in, but the engine would think
115
- # it had already written the data in. So for the time being
116
- # (and since write blocking is quite rare), go ahead and actually
117
- # block in write_nonblock.
148
+ # it had already written the data in.
149
+ #
150
+ # So for the time being (and since write blocking is quite rare),
151
+ # go ahead and actually block in write_nonblock.
152
+ #
118
153
  def write_nonblock(data, *_)
119
154
  write data
120
155
  end
@@ -125,11 +160,14 @@ module Puma
125
160
 
126
161
  def read_and_drop(timeout = 1)
127
162
  return :timeout unless IO.select([@socket], nil, nil, timeout)
128
- return :eof unless read_nonblock(1024)
129
- :drop
130
- rescue Errno::EAGAIN
131
- # do nothing
132
- :eagain
163
+ case @socket.read_nonblock(1024, exception: false)
164
+ when nil
165
+ :eof
166
+ when :wait_readable
167
+ :eagain
168
+ else
169
+ :drop
170
+ end
133
171
  end
134
172
 
135
173
  def should_drop_bytes?
@@ -141,9 +179,7 @@ module Puma
141
179
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
180
  # Don't let this socket hold this loop forever.
143
181
  # If it can't send more packets within 1s, then give up.
144
- while should_drop_bytes?
145
- return if [:timeout, :eof].include?(read_and_drop(1))
146
- end
182
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
147
183
  rescue IOError, SystemCallError
148
184
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
185
  # nothing
@@ -166,12 +202,13 @@ module Puma
166
202
  end
167
203
  end
168
204
 
169
- if defined?(JRUBY_VERSION)
205
+ if IS_JRUBY
206
+ OPENSSL_NO_SSL3 = false
207
+ OPENSSL_NO_TLS1 = false
208
+
170
209
  class SSLError < StandardError
171
210
  # Define this for jruby even though it isn't used.
172
211
  end
173
-
174
- def self.check; end
175
212
  end
176
213
 
177
214
  class Context
@@ -183,7 +220,7 @@ module Puma
183
220
  @no_tlsv1_1 = false
184
221
  end
185
222
 
186
- if defined?(JRUBY_VERSION)
223
+ if IS_JRUBY
187
224
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
225
  attr_reader :keystore
189
226
  attr_accessor :keystore_pass
@@ -228,13 +265,13 @@ module Puma
228
265
 
229
266
  # disables TLSv1
230
267
  def no_tlsv1=(tlsv1)
231
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
268
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
232
269
  @no_tlsv1 = tlsv1
233
270
  end
234
271
 
235
272
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
236
273
  def no_tlsv1_1=(tlsv1_1)
237
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
274
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
238
275
  @no_tlsv1_1 = tlsv1_1
239
276
  end
240
277
 
@@ -270,6 +307,11 @@ module Puma
270
307
  Socket.new io, engine
271
308
  end
272
309
 
310
+ # @version 5.0.0
311
+ def addr
312
+ @socket.addr
313
+ end
314
+
273
315
  def close
274
316
  @socket.close unless @socket.closed? # closed? call is for Windows
275
317
  end
@@ -2,9 +2,6 @@ module Puma
2
2
  module MiniSSL
3
3
  class ContextBuilder
4
4
  def initialize(params, events)
5
- require 'puma/minissl'
6
- MiniSSL.check
7
-
8
5
  @params = params
9
6
  @events = events
10
7
  end
@@ -15,7 +15,7 @@ module Puma
15
15
  # Mimics IO#read with no data.
16
16
  #
17
17
  def read(count = nil, _buffer = nil)
18
- (count && count > 0) ? nil : ""
18
+ count && count > 0 ? nil : ""
19
19
  end
20
20
 
21
21
  def rewind
@@ -10,7 +10,7 @@ module Puma
10
10
 
11
11
  def create(name)
12
12
  if cls = Plugins.find(name)
13
- plugin = cls.new(Plugin)
13
+ plugin = cls.new
14
14
  @instances << plugin
15
15
  return plugin
16
16
  end
@@ -104,17 +104,8 @@ module Puma
104
104
  Plugins.register name, cls
105
105
  end
106
106
 
107
- def initialize(loader)
108
- @loader = loader
109
- end
110
-
111
107
  def in_background(&blk)
112
108
  Plugins.add_background blk
113
109
  end
114
-
115
- def workers_supported?
116
- return false if Puma.jruby? || Puma.windows?
117
- true
118
- end
119
110
  end
120
111
  end
Binary file
@@ -67,10 +67,6 @@ module Puma::Rack
67
67
  options[:environment] = e
68
68
  }
69
69
 
70
- opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
71
- options[:daemonize] = d ? true : false
72
- }
73
-
74
70
  opts.on("-P", "--pid FILE", "file to store PID") { |f|
75
71
  options[:pid] = ::File.expand_path(f)
76
72
  }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'puma/util'
4
- require 'puma/minissl'
4
+ require 'puma/minissl' if ::Puma::HAS_SSL
5
5
 
6
6
  require 'nio'
7
7
 
@@ -189,7 +189,12 @@ module Puma
189
189
  if submon.value == @ready
190
190
  false
191
191
  else
192
- submon.value.close
192
+ if submon.value.can_close?
193
+ submon.value.close
194
+ else
195
+ # Pass remaining open client connections to the thread pool.
196
+ @app_pool << submon.value
197
+ end
193
198
  begin
194
199
  selector.deregister submon.value
195
200
  rescue IOError
@@ -247,7 +252,7 @@ module Puma
247
252
  c.close
248
253
  clear_monitor mon
249
254
 
250
- @events.ssl_error @server, addr, cert, e
255
+ @events.ssl_error e, addr, cert
251
256
 
252
257
  # The client doesn't know HTTP well
253
258
  rescue HttpParserError => e
@@ -258,7 +263,7 @@ module Puma
258
263
 
259
264
  clear_monitor mon
260
265
 
261
- @events.parse_error @server, c.env, e
266
+ @events.parse_error e, c
262
267
  rescue StandardError => e
263
268
  @server.lowlevel_error(e, c.env)
264
269
 
@@ -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,8 +48,6 @@ 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
@@ -64,30 +58,17 @@ module Puma
64
58
  control.min_threads = 0
65
59
  control.max_threads = 1
66
60
 
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
61
+ control.binder.parse [str], self, 'Starting control server'
86
62
 
87
63
  control.run
88
64
  @control = control
89
65
  end
90
66
 
67
+ # @version 5.0.0
68
+ def close_control_listeners
69
+ @control.binder.close_listeners if @control
70
+ end
71
+
91
72
  def ruby_engine
92
73
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
93
74
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -108,10 +89,6 @@ module Puma
108
89
  log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
109
90
  log "* Min threads: #{min_t}, max threads: #{max_t}"
110
91
  log "* Environment: #{ENV['RACK_ENV']}"
111
-
112
- if @options[:mode] == :tcp
113
- log "* Mode: Lopez Express (tcp)"
114
- end
115
92
  end
116
93
 
117
94
  def redirected_io?
@@ -150,7 +127,6 @@ module Puma
150
127
  exit 1
151
128
  end
152
129
 
153
- # Load the app before we daemonize.
154
130
  begin
155
131
  @app = @launcher.config.app
156
132
  rescue Exception => e
@@ -174,10 +150,6 @@ module Puma
174
150
  server.max_threads = max_t
175
151
  server.inherit_binder @launcher.binder
176
152
 
177
- if @options[:mode] == :tcp
178
- server.tcp_mode!
179
- end
180
-
181
153
  if @options[:early_hints]
182
154
  server.early_hints = true
183
155
  end
@@ -9,10 +9,8 @@ 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'
16
14
 
17
15
  require 'socket'
18
16
  require 'forwardable'
@@ -36,6 +34,7 @@ module Puma
36
34
 
37
35
  attr_reader :thread
38
36
  attr_reader :events
37
+ attr_reader :requests_count # @version 5.0.0
39
38
  attr_accessor :app
40
39
 
41
40
  attr_accessor :min_threads
@@ -57,8 +56,7 @@ module Puma
57
56
  @app = app
58
57
  @events = events
59
58
 
60
- @check, @notify = Puma::Util.pipe
61
-
59
+ @check, @notify = nil
62
60
  @status = :stop
63
61
 
64
62
  @min_threads = 0
@@ -85,24 +83,34 @@ module Puma
85
83
  @mode = :http
86
84
 
87
85
  @precheck_closing = true
86
+
87
+ @requests_count = 0
88
88
  end
89
89
 
90
90
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
91
 
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
92
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
93
93
 
94
94
  def inherit_binder(bind)
95
95
  @binder = bind
96
96
  end
97
97
 
98
- def tcp_mode!
99
- @mode = :tcp
98
+ class << self
99
+ # :nodoc:
100
+ # @version 5.0.0
101
+ def tcp_cork_supported?
102
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
103
+ Socket.const_defined?(:IPPROTO_TCP) &&
104
+ Socket.const_defined?(:TCP_CORK) &&
105
+ Socket.const_defined?(:TCP_INFO)
106
+ end
107
+ private :tcp_cork_supported?
100
108
  end
101
109
 
102
110
  # On Linux, use TCP_CORK to better control how the TCP stack
103
111
  # packetizes our stream. This improves both latency and throughput.
104
112
  #
105
- if RUBY_PLATFORM =~ /linux/
113
+ if tcp_cork_supported?
106
114
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
115
 
108
116
  # 6 == Socket::IPPROTO_TCP
@@ -110,7 +118,7 @@ module Puma
110
118
  # 1/0 == turn on/off
111
119
  def cork_socket(socket)
112
120
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
121
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
114
122
  rescue IOError, SystemCallError
115
123
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
116
124
  end
@@ -118,7 +126,7 @@ module Puma
118
126
 
119
127
  def uncork_socket(socket)
120
128
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
129
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
122
130
  rescue IOError, SystemCallError
123
131
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
124
132
  end
@@ -129,7 +137,7 @@ module Puma
129
137
  return false unless @precheck_closing
130
138
 
131
139
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
140
+ tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
141
  rescue IOError, SystemCallError
134
142
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
135
143
  @precheck_closing = false
@@ -172,107 +180,6 @@ module Puma
172
180
  @thread_pool and @thread_pool.pool_capacity
173
181
  end
174
182
 
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
183
  # Runs the server.
277
184
  #
278
185
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +193,9 @@ module Puma
286
193
 
287
194
  @status = :run
288
195
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
196
  @thread_pool = ThreadPool.new(@min_threads,
296
197
  @max_threads,
297
- IOBuffer) do |client, buffer|
198
+ ::Puma::IOBuffer) do |client, buffer|
298
199
 
299
200
  # Advertise this server into the thread
300
201
  Thread.current[ThreadLocalKey] = self
@@ -302,10 +203,10 @@ module Puma
302
203
  process_now = false
303
204
 
304
205
  begin
305
- if queue_requests
206
+ if @queue_requests
306
207
  process_now = client.eagerly_finish
307
208
  else
308
- client.finish
209
+ client.finish(@first_data_timeout)
309
210
  process_now = true
310
211
  end
311
212
  rescue MiniSSL::SSLError => e
@@ -315,14 +216,16 @@ module Puma
315
216
 
316
217
  client.close
317
218
 
318
- @events.ssl_error self, addr, cert, e
219
+ @events.ssl_error e, addr, cert
319
220
  rescue HttpParserError => e
320
221
  client.write_error(400)
321
222
  client.close
322
223
 
323
- @events.parse_error self, client.env, e
324
- rescue ConnectionError, EOFError
224
+ @events.parse_error e, client
225
+ rescue ConnectionError, EOFError => e
325
226
  client.close
227
+
228
+ @events.connection_error e, client
326
229
  else
327
230
  if process_now
328
231
  process_client client, buffer
@@ -331,11 +234,14 @@ module Puma
331
234
  @reactor.add client
332
235
  end
333
236
  end
237
+
238
+ process_now
334
239
  end
335
240
 
241
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
242
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
243
 
338
- if queue_requests
244
+ if @queue_requests
339
245
  @reactor = Reactor.new self, @thread_pool
340
246
  @reactor.run_in_thread
341
247
  end
@@ -362,6 +268,7 @@ module Puma
362
268
  end
363
269
 
364
270
  def handle_servers
271
+ @check, @notify = Puma::Util.pipe unless @notify
365
272
  begin
366
273
  check = @check
367
274
  sockets = [check] + @binder.ios
@@ -385,51 +292,49 @@ module Puma
385
292
  if sock == check
386
293
  break if handle_check
387
294
  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
295
+ pool.wait_until_not_full
296
+ pool.wait_for_less_busy_worker(
297
+ @options[:wait_for_less_busy_worker].to_f)
298
+
299
+ io = begin
300
+ sock.accept_nonblock
301
+ rescue IO::WaitReadable
302
+ next
412
303
  end
304
+ client = Client.new io, @binder.env(sock)
305
+ if remote_addr_value
306
+ client.peerip = remote_addr_value
307
+ elsif remote_addr_header
308
+ client.remote_addr_header = remote_addr_header
309
+ end
310
+ pool << client
413
311
  end
414
312
  end
415
313
  rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
314
+ @events.unknown_error e, nil, "Listen loop"
417
315
  end
418
316
  end
419
317
 
420
318
  @events.fire :state, @status
421
319
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
320
  if queue_requests
321
+ @queue_requests = false
424
322
  @reactor.clear!
425
323
  @reactor.shutdown
426
324
  end
325
+ graceful_shutdown if @status == :stop || @status == :restart
427
326
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
327
+ @events.unknown_error e, nil, "Exception handling servers"
430
328
  ensure
431
- @check.close
329
+ begin
330
+ @check.close unless @check.closed?
331
+ rescue Errno::EBADF, RuntimeError
332
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
333
+ # Errno::EBADF is infrequently raised
334
+ end
432
335
  @notify.close
336
+ @notify = nil
337
+ @check = nil
433
338
  end
434
339
 
435
340
  @events.fire :state, :done
@@ -476,7 +381,6 @@ module Puma
476
381
  close_socket = false
477
382
  return
478
383
  when true
479
- return unless @queue_requests
480
384
  buffer.reset
481
385
 
482
386
  ThreadPool.clean_thread_locals if clean_thread_locals
@@ -494,6 +398,7 @@ module Puma
494
398
  end
495
399
 
496
400
  unless client.reset(check_for_more_data)
401
+ return unless @queue_requests
497
402
  close_socket = false
498
403
  client.set_timeout @persistent_timeout
499
404
  @reactor.add client
@@ -516,7 +421,7 @@ module Puma
516
421
 
517
422
  close_socket = true
518
423
 
519
- @events.ssl_error self, addr, cert, e
424
+ @events.ssl_error e, addr, cert
520
425
 
521
426
  # The client doesn't know HTTP well
522
427
  rescue HttpParserError => e
@@ -524,7 +429,7 @@ module Puma
524
429
 
525
430
  client.write_error(400)
526
431
 
527
- @events.parse_error self, client.env, e
432
+ @events.parse_error e, client
528
433
 
529
434
  # Server error
530
435
  rescue StandardError => e
@@ -532,8 +437,7 @@ module Puma
532
437
 
533
438
  client.write_error(500)
534
439
 
535
- @events.unknown_error self, e, "Read"
536
-
440
+ @events.unknown_error e, nil, "Read"
537
441
  ensure
538
442
  buffer.reset
539
443
 
@@ -543,7 +447,7 @@ module Puma
543
447
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
544
448
  # Already closed
545
449
  rescue StandardError => e
546
- @events.unknown_error self, e, "Client"
450
+ @events.unknown_error e, nil, "Client"
547
451
  end
548
452
  end
549
453
  end
@@ -579,7 +483,7 @@ module Puma
579
483
 
580
484
  env[PATH_INFO] = env[REQUEST_PATH]
581
485
 
582
- # From http://www.ietf.org/rfc/rfc3875 :
486
+ # From https://www.ietf.org/rfc/rfc3875 :
583
487
  # "Script authors should be aware that the REMOTE_ADDR and
584
488
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
585
489
  # may not identify the ultimate source of the request.
@@ -626,6 +530,8 @@ module Puma
626
530
  #
627
531
  # Finally, it'll return +true+ on keep-alive connections.
628
532
  def handle_request(req, lines)
533
+ @requests_count +=1
534
+
629
535
  env = req.env
630
536
  client = req.io
631
537
 
@@ -666,12 +572,44 @@ module Puma
666
572
  end
667
573
 
668
574
  fast_write client, "\r\n".freeze
669
- rescue ConnectionError
575
+ rescue ConnectionError => e
576
+ @events.debug_error e
670
577
  # noop, if we lost the socket we just won't send the early hints
671
578
  end
672
579
  }
673
580
  end
674
581
 
582
+ # Fixup any headers with , in the name to have _ now. We emit
583
+ # headers with , in them during the parse phase to avoid ambiguity
584
+ # with the - to _ conversion for critical headers. But here for
585
+ # compatibility, we'll convert them back. This code is written to
586
+ # avoid allocation in the common case (ie there are no headers
587
+ # with , in their names), that's why it has the extra conditionals.
588
+
589
+ to_delete = nil
590
+ to_add = nil
591
+
592
+ env.each do |k,v|
593
+ if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
594
+ if to_delete
595
+ to_delete << k
596
+ else
597
+ to_delete = [k]
598
+ end
599
+
600
+ unless to_add
601
+ to_add = {}
602
+ end
603
+
604
+ to_add[k.tr(",", "_")] = v
605
+ end
606
+ end
607
+
608
+ if to_delete
609
+ to_delete.each { |k| env.delete(k) }
610
+ env.merge! to_add
611
+ end
612
+
675
613
  # A rack extension. If the app writes #call'ables to this
676
614
  # array, we will invoke them when the request is done.
677
615
  #
@@ -693,17 +631,14 @@ module Puma
693
631
  return :async
694
632
  end
695
633
  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"]
634
+ @events.unknown_error e, req, "Rack app"
635
+ @events.log "Detected force shutdown of a thread"
702
636
 
637
+ status, headers, res_body = lowlevel_error(e, env, 503)
703
638
  rescue Exception => e
704
- @events.unknown_error self, e, "Rack app", env
639
+ @events.unknown_error e, req, "Rack app"
705
640
 
706
- status, headers, res_body = lowlevel_error(e, env)
641
+ status, headers, res_body = lowlevel_error(e, env, 500)
707
642
  end
708
643
 
709
644
  content_length = nil
@@ -718,10 +653,10 @@ module Puma
718
653
  line_ending = LINE_END
719
654
  colon = COLON
720
655
 
721
- http_11 = if env[HTTP_VERSION] == HTTP_11
656
+ http_11 = env[HTTP_VERSION] == HTTP_11
657
+ if http_11
722
658
  allow_chunked = true
723
659
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
724
- include_keepalive_header = false
725
660
 
726
661
  # An optimization. The most common response is 200, so we can
727
662
  # reply with the proper 200 status without having to compute
@@ -735,11 +670,9 @@ module Puma
735
670
 
736
671
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
737
672
  end
738
- true
739
673
  else
740
674
  allow_chunked = false
741
675
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
742
- include_keepalive_header = keep_alive
743
676
 
744
677
  # Same optimization as above for HTTP/1.1
745
678
  #
@@ -751,9 +684,12 @@ module Puma
751
684
 
752
685
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
753
686
  end
754
- false
755
687
  end
756
688
 
689
+ # regardless of what the client wants, we always close the connection
690
+ # if running without request queueing
691
+ keep_alive &&= @queue_requests
692
+
757
693
  response_hijack = nil
758
694
 
759
695
  headers.each do |k, vs|
@@ -780,10 +716,15 @@ module Puma
780
716
  end
781
717
  end
782
718
 
783
- if include_keepalive_header
784
- lines << CONNECTION_KEEP_ALIVE
785
- elsif http_11 && !keep_alive
786
- lines << CONNECTION_CLOSE
719
+ # HTTP/1.1 & 1.0 assume different defaults:
720
+ # - HTTP 1.0 assumes the connection will be closed if not specified
721
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
722
+ # Only set the header if we're doing something which is not the default
723
+ # for this protocol version
724
+ if http_11
725
+ lines << CONNECTION_CLOSE if !keep_alive
726
+ else
727
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
787
728
  end
788
729
 
789
730
  if no_body
@@ -910,19 +851,21 @@ module Puma
910
851
 
911
852
  # A fallback rack response if +@app+ raises as exception.
912
853
  #
913
- def lowlevel_error(e, env)
854
+ def lowlevel_error(e, env, status=500)
914
855
  if handler = @options[:lowlevel_error_handler]
915
856
  if handler.arity == 1
916
857
  return handler.call(e)
917
- else
858
+ elsif handler.arity == 2
918
859
  return handler.call(e, env)
860
+ else
861
+ return handler.call(e, env, status)
919
862
  end
920
863
  end
921
864
 
922
865
  if @leak_stack_on_error
923
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
866
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
924
867
  else
925
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
868
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
926
869
  end
927
870
  end
928
871
 
@@ -980,9 +923,10 @@ module Puma
980
923
  end
981
924
 
982
925
  def notify_safely(message)
926
+ @check, @notify = Puma::Util.pipe unless @notify
983
927
  begin
984
928
  @notify << message
985
- rescue IOError
929
+ rescue IOError, NoMethodError, Errno::EPIPE
986
930
  # The server, in another thread, is shutting down
987
931
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
988
932
  rescue RuntimeError => e
@@ -1009,8 +953,9 @@ module Puma
1009
953
  @thread.join if @thread && sync
1010
954
  end
1011
955
 
1012
- def begin_restart
956
+ def begin_restart(sync=false)
1013
957
  notify_safely(RESTART_COMMAND)
958
+ @thread.join if @thread && sync
1014
959
  end
1015
960
 
1016
961
  def fast_write(io, str)
@@ -1048,5 +993,15 @@ module Puma
1048
993
  HTTP_INJECTION_REGEX =~ header_value.to_s
1049
994
  end
1050
995
  private :possible_header_injection?
996
+
997
+ # List of methods invoked by #stats.
998
+ # @version 5.0.0
999
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1000
+
1001
+ # Returns a hash of stats about the running server for reporting purposes.
1002
+ # @version 5.0.0
1003
+ def stats
1004
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1005
+ end
1051
1006
  end
1052
1007
  end