puma 4.3.6-java → 5.0.2-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.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1153 -518
  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/mini_ssl.c +15 -2
  20. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  23. data/ext/puma_http11/puma_http11.c +6 -38
  24. data/lib/puma.rb +20 -0
  25. data/lib/puma/app/status.rb +14 -1
  26. data/lib/puma/binder.rb +90 -68
  27. data/lib/puma/cli.rb +7 -15
  28. data/lib/puma/client.rb +62 -13
  29. data/lib/puma/cluster.rb +193 -74
  30. data/lib/puma/commonlogger.rb +2 -2
  31. data/lib/puma/configuration.rb +31 -42
  32. data/lib/puma/const.rb +3 -3
  33. data/lib/puma/control_cli.rb +29 -17
  34. data/lib/puma/detect.rb +17 -0
  35. data/lib/puma/dsl.rb +144 -70
  36. data/lib/puma/error_logger.rb +97 -0
  37. data/lib/puma/events.rb +37 -31
  38. data/lib/puma/io_buffer.rb +9 -2
  39. data/lib/puma/jruby_restart.rb +0 -58
  40. data/lib/puma/launcher.rb +57 -31
  41. data/lib/puma/minissl.rb +68 -18
  42. data/lib/puma/minissl/context_builder.rb +0 -3
  43. data/lib/puma/null_io.rb +1 -1
  44. data/lib/puma/plugin.rb +1 -10
  45. data/lib/puma/puma_http11.jar +0 -0
  46. data/lib/puma/rack/builder.rb +0 -4
  47. data/lib/puma/reactor.rb +10 -16
  48. data/lib/puma/runner.rb +8 -36
  49. data/lib/puma/server.rb +161 -218
  50. data/lib/puma/single.rb +8 -64
  51. data/lib/puma/state_file.rb +6 -3
  52. data/lib/puma/thread_pool.rb +116 -51
  53. data/lib/puma/util.rb +1 -0
  54. data/lib/rack/handler/puma.rb +1 -3
  55. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  56. metadata +17 -19
  57. data/docs/tcp_mode.md +0 -96
  58. data/ext/puma_http11/io_buffer.c +0 -155
  59. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  60. data/lib/puma/tcp_logger.rb +0 -41
  61. data/tools/jungle/README.md +0 -19
  62. data/tools/jungle/init.d/README.md +0 -61
  63. data/tools/jungle/init.d/puma +0 -421
  64. 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
@@ -14,6 +24,7 @@ module Puma
14
24
  @peercert = nil
15
25
  end
16
26
 
27
+ # @!attribute [r] to_io
17
28
  def to_io
18
29
  @socket
19
30
  end
@@ -22,6 +33,27 @@ module Puma
22
33
  @socket.closed?
23
34
  end
24
35
 
36
+ # Returns a two element array,
37
+ # first is protocol version (SSL_get_version),
38
+ # second is 'handshake' state (SSL_state_string)
39
+ #
40
+ # Used for dropping tcp connections to ssl.
41
+ # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
42
+ # @!attribute [r] ssl_version_state
43
+ # @version 5.0.0
44
+ #
45
+ def ssl_version_state
46
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
47
+ end
48
+
49
+ # Used to check the handshake status, in particular when a TCP connection
50
+ # is made with TLSv1.3 as an available protocol
51
+ # @version 5.0.0
52
+ def bad_tlsv1_3?
53
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
54
+ end
55
+ private :bad_tlsv1_3?
56
+
25
57
  def readpartial(size)
26
58
  while true
27
59
  output = @engine.read
@@ -41,6 +73,7 @@ module Puma
41
73
 
42
74
  def engine_read_all
43
75
  output = @engine.read
76
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
77
  while output and additional_output = @engine.read
45
78
  output << additional_output
46
79
  end
@@ -107,14 +140,18 @@ module Puma
107
140
  alias_method :<<, :write
108
141
 
109
142
  # This is a temporary fix to deal with websockets code using
110
- # write_nonblock. The problem with implementing it properly
143
+ # write_nonblock.
144
+
145
+ # The problem with implementing it properly
111
146
  # is that it means we'd have to have the ability to rewind
112
147
  # an engine because after we write+extract, the socket
113
148
  # write_nonblock call might raise an exception and later
114
149
  # 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.
150
+ # it had already written the data in.
151
+ #
152
+ # So for the time being (and since write blocking is quite rare),
153
+ # go ahead and actually block in write_nonblock.
154
+ #
118
155
  def write_nonblock(data, *_)
119
156
  write data
120
157
  end
@@ -125,11 +162,14 @@ module Puma
125
162
 
126
163
  def read_and_drop(timeout = 1)
127
164
  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
165
+ case @socket.read_nonblock(1024, exception: false)
166
+ when nil
167
+ :eof
168
+ when :wait_readable
169
+ :eagain
170
+ else
171
+ :drop
172
+ end
133
173
  end
134
174
 
135
175
  def should_drop_bytes?
@@ -141,9 +181,7 @@ module Puma
141
181
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
182
  # Don't let this socket hold this loop forever.
143
183
  # 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
184
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
147
185
  rescue IOError, SystemCallError
148
186
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
187
  # nothing
@@ -152,10 +190,12 @@ module Puma
152
190
  end
153
191
  end
154
192
 
193
+ # @!attribute [r] peeraddr
155
194
  def peeraddr
156
195
  @socket.peeraddr
157
196
  end
158
197
 
198
+ # @!attribute [r] peercert
159
199
  def peercert
160
200
  return @peercert if @peercert
161
201
 
@@ -166,12 +206,13 @@ module Puma
166
206
  end
167
207
  end
168
208
 
169
- if defined?(JRUBY_VERSION)
209
+ if IS_JRUBY
210
+ OPENSSL_NO_SSL3 = false
211
+ OPENSSL_NO_TLS1 = false
212
+
170
213
  class SSLError < StandardError
171
214
  # Define this for jruby even though it isn't used.
172
215
  end
173
-
174
- def self.check; end
175
216
  end
176
217
 
177
218
  class Context
@@ -183,7 +224,7 @@ module Puma
183
224
  @no_tlsv1_1 = false
184
225
  end
185
226
 
186
- if defined?(JRUBY_VERSION)
227
+ if IS_JRUBY
187
228
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
229
  attr_reader :keystore
189
230
  attr_accessor :keystore_pass
@@ -227,14 +268,16 @@ module Puma
227
268
  end
228
269
 
229
270
  # disables TLSv1
271
+ # @!attribute [w] no_tlsv1=
230
272
  def no_tlsv1=(tlsv1)
231
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
273
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
232
274
  @no_tlsv1 = tlsv1
233
275
  end
234
276
 
235
277
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
278
+ # @!attribute [w] no_tlsv1_1=
236
279
  def no_tlsv1_1=(tlsv1_1)
237
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
280
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
238
281
  @no_tlsv1_1 = tlsv1_1
239
282
  end
240
283
 
@@ -250,6 +293,7 @@ module Puma
250
293
  @ctx = ctx
251
294
  end
252
295
 
296
+ # @!attribute [r] to_io
253
297
  def to_io
254
298
  @socket
255
299
  end
@@ -270,6 +314,12 @@ module Puma
270
314
  Socket.new io, engine
271
315
  end
272
316
 
317
+ # @!attribute [r] addr
318
+ # @version 5.0.0
319
+ def addr
320
+ @socket.addr
321
+ end
322
+
273
323
  def close
274
324
  @socket.close unless @socket.closed? # closed? call is for Windows
275
325
  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
@@ -232,23 +237,12 @@ module Puma
232
237
 
233
238
  # SSL handshake failure
234
239
  rescue MiniSSL::SSLError => e
235
- @server.lowlevel_error(e, c.env)
236
-
237
- ssl_socket = c.io
238
- begin
239
- addr = ssl_socket.peeraddr.last
240
- # EINVAL can happen when browser closes socket w/security exception
241
- rescue IOError, Errno::EINVAL
242
- addr = "<unknown>"
243
- end
244
-
245
- cert = ssl_socket.peercert
240
+ @server.lowlevel_error e, c.env
241
+ @events.ssl_error e, c.io
246
242
 
247
243
  c.close
248
244
  clear_monitor mon
249
245
 
250
- @events.ssl_error @server, addr, cert, e
251
-
252
246
  # The client doesn't know HTTP well
253
247
  rescue HttpParserError => e
254
248
  @server.lowlevel_error(e, c.env)
@@ -258,7 +252,7 @@ module Puma
258
252
 
259
253
  clear_monitor mon
260
254
 
261
- @events.parse_error @server, c.env, e
255
+ @events.parse_error e, c
262
256
  rescue StandardError => e
263
257
  @server.lowlevel_error(e, c.env)
264
258
 
@@ -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,44 @@ module Puma
85
83
  @mode = :http
86
84
 
87
85
  @precheck_closing = true
86
+
87
+ @requests_count = 0
88
+
89
+ @shutdown_mutex = Mutex.new
88
90
  end
89
91
 
90
92
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
93
 
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
94
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
93
95
 
94
96
  def inherit_binder(bind)
95
97
  @binder = bind
96
98
  end
97
99
 
98
- def tcp_mode!
99
- @mode = :tcp
100
+ class << self
101
+ # :nodoc:
102
+ # @version 5.0.0
103
+ def tcp_cork_supported?
104
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
105
+ Socket.const_defined?(:IPPROTO_TCP) &&
106
+ Socket.const_defined?(:TCP_CORK)
107
+ end
108
+
109
+ # :nodoc:
110
+ # @version 5.0.0
111
+ def closed_socket_supported?
112
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
113
+ Socket.const_defined?(:IPPROTO_TCP) &&
114
+ Socket.const_defined?(:TCP_INFO)
115
+ end
116
+ private :tcp_cork_supported?
117
+ private :closed_socket_supported?
100
118
  end
101
119
 
102
120
  # On Linux, use TCP_CORK to better control how the TCP stack
103
121
  # packetizes our stream. This improves both latency and throughput.
104
122
  #
105
- if RUBY_PLATFORM =~ /linux/
123
+ if tcp_cork_supported?
106
124
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
125
 
108
126
  # 6 == Socket::IPPROTO_TCP
@@ -110,7 +128,7 @@ module Puma
110
128
  # 1/0 == turn on/off
111
129
  def cork_socket(socket)
112
130
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
131
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
114
132
  rescue IOError, SystemCallError
115
133
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
116
134
  end
@@ -118,18 +136,26 @@ module Puma
118
136
 
119
137
  def uncork_socket(socket)
120
138
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
139
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
122
140
  rescue IOError, SystemCallError
123
141
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
124
142
  end
125
143
  end
144
+ else
145
+ def cork_socket(socket)
146
+ end
126
147
 
148
+ def uncork_socket(socket)
149
+ end
150
+ end
151
+
152
+ if closed_socket_supported?
127
153
  def closed_socket?(socket)
128
154
  return false unless socket.kind_of? TCPSocket
129
155
  return false unless @precheck_closing
130
156
 
131
157
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
158
+ tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
159
  rescue IOError, SystemCallError
134
160
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
135
161
  @precheck_closing = false
@@ -141,12 +167,6 @@ module Puma
141
167
  end
142
168
  end
143
169
  else
144
- def cork_socket(socket)
145
- end
146
-
147
- def uncork_socket(socket)
148
- end
149
-
150
170
  def closed_socket?(socket)
151
171
  false
152
172
  end
@@ -172,107 +192,6 @@ module Puma
172
192
  @thread_pool and @thread_pool.pool_capacity
173
193
  end
174
194
 
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
195
  # Runs the server.
277
196
  #
278
197
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +205,9 @@ module Puma
286
205
 
287
206
  @status = :run
288
207
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
208
  @thread_pool = ThreadPool.new(@min_threads,
296
209
  @max_threads,
297
- IOBuffer) do |client, buffer|
210
+ ::Puma::IOBuffer) do |client, buffer|
298
211
 
299
212
  # Advertise this server into the thread
300
213
  Thread.current[ThreadLocalKey] = self
@@ -302,40 +215,48 @@ module Puma
302
215
  process_now = false
303
216
 
304
217
  begin
305
- if queue_requests
218
+ if @queue_requests
306
219
  process_now = client.eagerly_finish
307
220
  else
308
- client.finish
221
+ @thread_pool.with_force_shutdown do
222
+ client.finish(@first_data_timeout)
223
+ end
309
224
  process_now = true
310
225
  end
311
226
  rescue MiniSSL::SSLError => e
312
- ssl_socket = client.io
313
- addr = ssl_socket.peeraddr.last
314
- cert = ssl_socket.peercert
315
-
227
+ @events.ssl_error e, client.io
316
228
  client.close
317
229
 
318
- @events.ssl_error self, addr, cert, e
319
230
  rescue HttpParserError => e
320
231
  client.write_error(400)
321
232
  client.close
322
233
 
323
- @events.parse_error self, client.env, e
324
- rescue ConnectionError, EOFError
234
+ @events.parse_error e, client
235
+ rescue EOFError => e
325
236
  client.close
237
+
238
+ # Swallow, do not log
239
+ rescue ConnectionError, ThreadPool::ForceShutdown => e
240
+ client.close
241
+
242
+ @events.connection_error e, client
326
243
  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
244
+ process_now ||= @shutdown_mutex.synchronize do
245
+ next true unless @queue_requests
246
+ client.set_timeout @first_data_timeout
247
+ @reactor.add client
248
+ false
249
+ end
250
+ process_client client, buffer if process_now
333
251
  end
252
+
253
+ process_now
334
254
  end
335
255
 
256
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
257
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
258
 
338
- if queue_requests
259
+ if @queue_requests
339
260
  @reactor = Reactor.new self, @thread_pool
340
261
  @reactor.run_in_thread
341
262
  end
@@ -362,6 +283,7 @@ module Puma
362
283
  end
363
284
 
364
285
  def handle_servers
286
+ @check, @notify = Puma::Util.pipe unless @notify
365
287
  begin
366
288
  check = @check
367
289
  sockets = [check] + @binder.ios
@@ -385,51 +307,51 @@ module Puma
385
307
  if sock == check
386
308
  break if handle_check
387
309
  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
310
+ pool.wait_until_not_full
311
+ pool.wait_for_less_busy_worker(
312
+ @options[:wait_for_less_busy_worker].to_f)
313
+
314
+ io = begin
315
+ sock.accept_nonblock
316
+ rescue IO::WaitReadable
317
+ next
318
+ end
319
+ client = Client.new io, @binder.env(sock)
320
+ if remote_addr_value
321
+ client.peerip = remote_addr_value
322
+ elsif remote_addr_header
323
+ client.remote_addr_header = remote_addr_header
412
324
  end
325
+ pool << client
413
326
  end
414
327
  end
415
328
  rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
329
+ @events.unknown_error e, nil, "Listen loop"
417
330
  end
418
331
  end
419
332
 
420
333
  @events.fire :state, @status
421
334
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
335
  if queue_requests
336
+ @shutdown_mutex.synchronize do
337
+ @queue_requests = false
338
+ end
424
339
  @reactor.clear!
425
340
  @reactor.shutdown
426
341
  end
342
+ graceful_shutdown if @status == :stop || @status == :restart
427
343
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
344
+ @events.unknown_error e, nil, "Exception handling servers"
430
345
  ensure
431
- @check.close
346
+ begin
347
+ @check.close unless @check.closed?
348
+ rescue Errno::EBADF, RuntimeError
349
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
350
+ # Errno::EBADF is infrequently raised
351
+ end
432
352
  @notify.close
353
+ @notify = nil
354
+ @check = nil
433
355
  end
434
356
 
435
357
  @events.fire :state, :done
@@ -476,7 +398,6 @@ module Puma
476
398
  close_socket = false
477
399
  return
478
400
  when true
479
- return unless @queue_requests
480
401
  buffer.reset
481
402
 
482
403
  ThreadPool.clean_thread_locals if clean_thread_locals
@@ -493,38 +414,39 @@ module Puma
493
414
  check_for_more_data = false
494
415
  end
495
416
 
496
- unless client.reset(check_for_more_data)
497
- close_socket = false
498
- client.set_timeout @persistent_timeout
499
- @reactor.add client
500
- return
417
+ next_request_ready = @thread_pool.with_force_shutdown do
418
+ client.reset(check_for_more_data)
419
+ end
420
+
421
+ unless next_request_ready
422
+ @shutdown_mutex.synchronize do
423
+ return unless @queue_requests
424
+ close_socket = false
425
+ client.set_timeout @persistent_timeout
426
+ @reactor.add client
427
+ return
428
+ end
501
429
  end
502
430
  end
503
431
  end
504
432
 
505
433
  # The client disconnected while we were reading data
506
- rescue ConnectionError
434
+ rescue ConnectionError, ThreadPool::ForceShutdown
507
435
  # Swallow them. The ensure tries to close +client+ down
508
436
 
509
437
  # SSL handshake error
510
438
  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
-
439
+ lowlevel_error e, client.env
440
+ @events.ssl_error e, client.io
517
441
  close_socket = true
518
442
 
519
- @events.ssl_error self, addr, cert, e
520
-
521
443
  # The client doesn't know HTTP well
522
444
  rescue HttpParserError => e
523
445
  lowlevel_error(e, client.env)
524
446
 
525
447
  client.write_error(400)
526
448
 
527
- @events.parse_error self, client.env, e
449
+ @events.parse_error e, client
528
450
 
529
451
  # Server error
530
452
  rescue StandardError => e
@@ -532,8 +454,7 @@ module Puma
532
454
 
533
455
  client.write_error(500)
534
456
 
535
- @events.unknown_error self, e, "Read"
536
-
457
+ @events.unknown_error e, nil, "Read"
537
458
  ensure
538
459
  buffer.reset
539
460
 
@@ -543,7 +464,7 @@ module Puma
543
464
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
544
465
  # Already closed
545
466
  rescue StandardError => e
546
- @events.unknown_error self, e, "Client"
467
+ @events.unknown_error e, nil, "Client"
547
468
  end
548
469
  end
549
470
  end
@@ -579,7 +500,7 @@ module Puma
579
500
 
580
501
  env[PATH_INFO] = env[REQUEST_PATH]
581
502
 
582
- # From http://www.ietf.org/rfc/rfc3875 :
503
+ # From https://www.ietf.org/rfc/rfc3875 :
583
504
  # "Script authors should be aware that the REMOTE_ADDR and
584
505
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
585
506
  # may not identify the ultimate source of the request.
@@ -626,6 +547,8 @@ module Puma
626
547
  #
627
548
  # Finally, it'll return +true+ on keep-alive connections.
628
549
  def handle_request(req, lines)
550
+ @requests_count +=1
551
+
629
552
  env = req.env
630
553
  client = req.io
631
554
 
@@ -666,7 +589,8 @@ module Puma
666
589
  end
667
590
 
668
591
  fast_write client, "\r\n".freeze
669
- rescue ConnectionError
592
+ rescue ConnectionError => e
593
+ @events.debug_error e
670
594
  # noop, if we lost the socket we just won't send the early hints
671
595
  end
672
596
  }
@@ -694,7 +618,7 @@ module Puma
694
618
  to_add = {}
695
619
  end
696
620
 
697
- to_add[k.gsub(",", "_")] = v
621
+ to_add[k.tr(",", "_")] = v
698
622
  end
699
623
  end
700
624
 
@@ -710,7 +634,9 @@ module Puma
710
634
 
711
635
  begin
712
636
  begin
713
- status, headers, res_body = @app.call(env)
637
+ status, headers, res_body = @thread_pool.with_force_shutdown do
638
+ @app.call(env)
639
+ end
714
640
 
715
641
  return :async if req.hijacked
716
642
 
@@ -724,17 +650,14 @@ module Puma
724
650
  return :async
725
651
  end
726
652
  rescue ThreadPool::ForceShutdown => e
727
- @events.log "Detected force shutdown of a thread, returning 503"
728
- @events.unknown_error self, e, "Rack app"
729
-
730
- status = 503
731
- headers = {}
732
- res_body = ["Request was internally terminated early\n"]
653
+ @events.unknown_error e, req, "Rack app"
654
+ @events.log "Detected force shutdown of a thread"
733
655
 
656
+ status, headers, res_body = lowlevel_error(e, env, 503)
734
657
  rescue Exception => e
735
- @events.unknown_error self, e, "Rack app", env
658
+ @events.unknown_error e, req, "Rack app"
736
659
 
737
- status, headers, res_body = lowlevel_error(e, env)
660
+ status, headers, res_body = lowlevel_error(e, env, 500)
738
661
  end
739
662
 
740
663
  content_length = nil
@@ -749,10 +672,10 @@ module Puma
749
672
  line_ending = LINE_END
750
673
  colon = COLON
751
674
 
752
- http_11 = if env[HTTP_VERSION] == HTTP_11
675
+ http_11 = env[HTTP_VERSION] == HTTP_11
676
+ if http_11
753
677
  allow_chunked = true
754
678
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
755
- include_keepalive_header = false
756
679
 
757
680
  # An optimization. The most common response is 200, so we can
758
681
  # reply with the proper 200 status without having to compute
@@ -766,11 +689,9 @@ module Puma
766
689
 
767
690
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
768
691
  end
769
- true
770
692
  else
771
693
  allow_chunked = false
772
694
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
773
- include_keepalive_header = keep_alive
774
695
 
775
696
  # Same optimization as above for HTTP/1.1
776
697
  #
@@ -782,9 +703,12 @@ module Puma
782
703
 
783
704
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
784
705
  end
785
- false
786
706
  end
787
707
 
708
+ # regardless of what the client wants, we always close the connection
709
+ # if running without request queueing
710
+ keep_alive &&= @queue_requests
711
+
788
712
  response_hijack = nil
789
713
 
790
714
  headers.each do |k, vs|
@@ -811,10 +735,15 @@ module Puma
811
735
  end
812
736
  end
813
737
 
814
- if include_keepalive_header
815
- lines << CONNECTION_KEEP_ALIVE
816
- elsif http_11 && !keep_alive
817
- lines << CONNECTION_CLOSE
738
+ # HTTP/1.1 & 1.0 assume different defaults:
739
+ # - HTTP 1.0 assumes the connection will be closed if not specified
740
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
741
+ # Only set the header if we're doing something which is not the default
742
+ # for this protocol version
743
+ if http_11
744
+ lines << CONNECTION_CLOSE if !keep_alive
745
+ else
746
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
818
747
  end
819
748
 
820
749
  if no_body
@@ -941,19 +870,21 @@ module Puma
941
870
 
942
871
  # A fallback rack response if +@app+ raises as exception.
943
872
  #
944
- def lowlevel_error(e, env)
873
+ def lowlevel_error(e, env, status=500)
945
874
  if handler = @options[:lowlevel_error_handler]
946
875
  if handler.arity == 1
947
876
  return handler.call(e)
948
- else
877
+ elsif handler.arity == 2
949
878
  return handler.call(e, env)
879
+ else
880
+ return handler.call(e, env, status)
950
881
  end
951
882
  end
952
883
 
953
884
  if @leak_stack_on_error
954
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
885
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
955
886
  else
956
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
887
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
957
888
  end
958
889
  end
959
890
 
@@ -1003,7 +934,7 @@ module Puma
1003
934
 
1004
935
  if @thread_pool
1005
936
  if timeout = @options[:force_shutdown_after]
1006
- @thread_pool.shutdown timeout.to_i
937
+ @thread_pool.shutdown timeout.to_f
1007
938
  else
1008
939
  @thread_pool.shutdown
1009
940
  end
@@ -1011,9 +942,10 @@ module Puma
1011
942
  end
1012
943
 
1013
944
  def notify_safely(message)
945
+ @check, @notify = Puma::Util.pipe unless @notify
1014
946
  begin
1015
947
  @notify << message
1016
- rescue IOError
948
+ rescue IOError, NoMethodError, Errno::EPIPE
1017
949
  # The server, in another thread, is shutting down
1018
950
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1019
951
  rescue RuntimeError => e
@@ -1040,8 +972,9 @@ module Puma
1040
972
  @thread.join if @thread && sync
1041
973
  end
1042
974
 
1043
- def begin_restart
975
+ def begin_restart(sync=false)
1044
976
  notify_safely(RESTART_COMMAND)
977
+ @thread.join if @thread && sync
1045
978
  end
1046
979
 
1047
980
  def fast_write(io, str)
@@ -1079,5 +1012,15 @@ module Puma
1079
1012
  HTTP_INJECTION_REGEX =~ header_value.to_s
1080
1013
  end
1081
1014
  private :possible_header_injection?
1015
+
1016
+ # List of methods invoked by #stats.
1017
+ # @version 5.0.0
1018
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1019
+
1020
+ # Returns a hash of stats about the running server for reporting purposes.
1021
+ # @version 5.0.0
1022
+ def stats
1023
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1024
+ end
1082
1025
  end
1083
1026
  end