puma 4.3.5-java → 5.0.1-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 +1149 -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/http11_parser.c +64 -64
  20. data/ext/puma_http11/mini_ssl.c +15 -2
  21. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  22. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  24. data/ext/puma_http11/puma_http11.c +7 -38
  25. data/lib/puma.rb +20 -0
  26. data/lib/puma/app/status.rb +18 -3
  27. data/lib/puma/binder.rb +90 -68
  28. data/lib/puma/cli.rb +7 -15
  29. data/lib/puma/client.rb +62 -13
  30. data/lib/puma/cluster.rb +193 -74
  31. data/lib/puma/commonlogger.rb +2 -2
  32. data/lib/puma/configuration.rb +31 -42
  33. data/lib/puma/const.rb +3 -3
  34. data/lib/puma/control_cli.rb +29 -17
  35. data/lib/puma/detect.rb +17 -0
  36. data/lib/puma/dsl.rb +144 -70
  37. data/lib/puma/error_logger.rb +97 -0
  38. data/lib/puma/events.rb +37 -31
  39. data/lib/puma/io_buffer.rb +9 -2
  40. data/lib/puma/jruby_restart.rb +0 -58
  41. data/lib/puma/launcher.rb +57 -31
  42. data/lib/puma/minissl.rb +68 -18
  43. data/lib/puma/minissl/context_builder.rb +0 -3
  44. data/lib/puma/null_io.rb +1 -1
  45. data/lib/puma/plugin.rb +1 -10
  46. data/lib/puma/puma_http11.jar +0 -0
  47. data/lib/puma/rack/builder.rb +0 -4
  48. data/lib/puma/reactor.rb +10 -16
  49. data/lib/puma/runner.rb +13 -54
  50. data/lib/puma/server.rb +193 -241
  51. data/lib/puma/single.rb +8 -64
  52. data/lib/puma/state_file.rb +6 -3
  53. data/lib/puma/thread_pool.rb +116 -51
  54. data/lib/puma/util.rb +1 -0
  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
@@ -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,42 +48,27 @@ module Puma
52
48
 
53
49
  require 'puma/app/status'
54
50
 
55
- uri = URI.parse str
56
-
57
51
  if token = @options[:control_auth_token]
58
52
  token = nil if token.empty? || token == 'none'
59
53
  end
60
54
 
61
55
  app = Puma::App::Status.new @launcher, token
62
56
 
63
- control = Puma::Server.new app, @launcher.events
64
- control.min_threads = 0
65
- control.max_threads = 1
66
-
67
- case uri.scheme
68
- when "ssl"
69
- log "* Starting control server on #{str}"
70
- params = Util.parse_query uri.query
71
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
72
-
73
- control.add_ssl_listener uri.host, uri.port, ctx
74
- when "tcp"
75
- log "* Starting control server on #{str}"
76
- control.add_tcp_listener uri.host, uri.port
77
- when "unix"
78
- log "* Starting control server on #{str}"
79
- path = "#{uri.host}#{uri.path}"
80
- mask = @options[:control_url_umask]
81
-
82
- control.add_unix_listener path, mask
83
- else
84
- error "Invalid control URI: #{str}"
85
- end
57
+ control = Puma::Server.new app, @launcher.events,
58
+ { min_threads: 0, max_threads: 1 }
59
+
60
+ control.binder.parse [str], self, 'Starting control server'
86
61
 
87
62
  control.run
88
63
  @control = control
89
64
  end
90
65
 
66
+ # @version 5.0.0
67
+ def close_control_listeners
68
+ @control.binder.close_listeners if @control
69
+ end
70
+
71
+ # @!attribute [r] ruby_engine
91
72
  def ruby_engine
92
73
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
93
74
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -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
@@ -161,31 +137,14 @@ module Puma
161
137
  @launcher.binder.parse @options[:binds], self
162
138
  end
163
139
 
140
+ # @!attribute [r] app
164
141
  def app
165
142
  @app ||= @launcher.config.app
166
143
  end
167
144
 
168
145
  def start_server
169
- min_t = @options[:min_threads]
170
- max_t = @options[:max_threads]
171
-
172
146
  server = Puma::Server.new app, @launcher.events, @options
173
- server.min_threads = min_t
174
- server.max_threads = max_t
175
147
  server.inherit_binder @launcher.binder
176
-
177
- if @options[:mode] == :tcp
178
- server.tcp_mode!
179
- end
180
-
181
- if @options[:early_hints]
182
- server.early_hints = true
183
- end
184
-
185
- unless development? || test?
186
- server.leak_stack_on_error = false
187
- end
188
-
189
148
  server
190
149
  end
191
150
  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,14 +34,21 @@ module Puma
36
34
 
37
35
  attr_reader :thread
38
36
  attr_reader :events
37
+ attr_reader :min_threads, :max_threads # for #stats
38
+ attr_reader :requests_count # @version 5.0.0
39
+
40
+ # the following may be deprecated in the future
41
+ attr_reader :auto_trim_time
42
+ attr_reader :first_data_timeout
43
+ attr_reader :persistent_timeout
44
+ attr_reader :reaping_time
45
+
39
46
  attr_accessor :app
47
+ attr_accessor :binder
40
48
 
41
- attr_accessor :min_threads
42
- attr_accessor :max_threads
43
- attr_accessor :persistent_timeout
44
- attr_accessor :auto_trim_time
45
- attr_accessor :reaping_time
46
- attr_accessor :first_data_timeout
49
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
50
+
51
+ ThreadLocalKey = :puma_server
47
52
 
48
53
  # Create a server for the rack app +app+.
49
54
  #
@@ -53,56 +58,80 @@ module Puma
53
58
  # Server#run returns a thread that you can join on to wait for the server
54
59
  # to do its work.
55
60
  #
61
+ # @note Several instance variables exist so they are available for testing,
62
+ # and have default values set via +fetch+. Normally the values are set via
63
+ # `::Puma::Configuration.puma_default_options`.
64
+ #
56
65
  def initialize(app, events=Events.stdio, options={})
57
66
  @app = app
58
67
  @events = events
59
68
 
60
- @check, @notify = Puma::Util.pipe
61
-
69
+ @check, @notify = nil
62
70
  @status = :stop
63
71
 
64
- @min_threads = 0
65
- @max_threads = 16
66
72
  @auto_trim_time = 30
67
73
  @reaping_time = 1
68
74
 
69
75
  @thread = nil
70
76
  @thread_pool = nil
71
- @early_hints = nil
72
77
 
73
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
74
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
78
+ @options = options
75
79
 
76
- @binder = Binder.new(events)
80
+ @early_hints = options.fetch :early_hints, nil
81
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
82
+ @min_threads = options.fetch :min_threads, 0
83
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
84
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
85
+ @queue_requests = options.fetch :queue_requests, true
77
86
 
78
- @leak_stack_on_error = true
87
+ @leak_stack_on_error = !!(@options[:environment] =~ /\A(development|test)\z/)
79
88
 
80
- @options = options
81
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
89
+ @binder = Binder.new(events)
82
90
 
83
91
  ENV['RACK_ENV'] ||= "development"
84
92
 
85
93
  @mode = :http
86
94
 
87
95
  @precheck_closing = true
88
- end
89
96
 
90
- attr_accessor :binder, :leak_stack_on_error, :early_hints
97
+ @requests_count = 0
91
98
 
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
99
+ @shutdown_mutex = Mutex.new
100
+ end
93
101
 
94
102
  def inherit_binder(bind)
95
103
  @binder = bind
96
104
  end
97
105
 
98
- def tcp_mode!
99
- @mode = :tcp
106
+ class << self
107
+ # @!attribute [r] current
108
+ def current
109
+ Thread.current[ThreadLocalKey]
110
+ end
111
+
112
+ # :nodoc:
113
+ # @version 5.0.0
114
+ def tcp_cork_supported?
115
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
116
+ Socket.const_defined?(:IPPROTO_TCP) &&
117
+ Socket.const_defined?(:TCP_CORK)
118
+ end
119
+
120
+ # :nodoc:
121
+ # @version 5.0.0
122
+ def closed_socket_supported?
123
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
124
+ Socket.const_defined?(:IPPROTO_TCP) &&
125
+ Socket.const_defined?(:TCP_INFO)
126
+ end
127
+ private :tcp_cork_supported?
128
+ private :closed_socket_supported?
100
129
  end
101
130
 
102
131
  # On Linux, use TCP_CORK to better control how the TCP stack
103
132
  # packetizes our stream. This improves both latency and throughput.
104
133
  #
105
- if RUBY_PLATFORM =~ /linux/
134
+ if tcp_cork_supported?
106
135
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
136
 
108
137
  # 6 == Socket::IPPROTO_TCP
@@ -110,7 +139,7 @@ module Puma
110
139
  # 1/0 == turn on/off
111
140
  def cork_socket(socket)
112
141
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
142
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
114
143
  rescue IOError, SystemCallError
115
144
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
116
145
  end
@@ -118,18 +147,26 @@ module Puma
118
147
 
119
148
  def uncork_socket(socket)
120
149
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
150
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
122
151
  rescue IOError, SystemCallError
123
152
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
124
153
  end
125
154
  end
155
+ else
156
+ def cork_socket(socket)
157
+ end
126
158
 
159
+ def uncork_socket(socket)
160
+ end
161
+ end
162
+
163
+ if closed_socket_supported?
127
164
  def closed_socket?(socket)
128
165
  return false unless socket.kind_of? TCPSocket
129
166
  return false unless @precheck_closing
130
167
 
131
168
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
169
+ tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
170
  rescue IOError, SystemCallError
134
171
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
135
172
  @precheck_closing = false
@@ -141,21 +178,17 @@ module Puma
141
178
  end
142
179
  end
143
180
  else
144
- def cork_socket(socket)
145
- end
146
-
147
- def uncork_socket(socket)
148
- end
149
-
150
181
  def closed_socket?(socket)
151
182
  false
152
183
  end
153
184
  end
154
185
 
186
+ # @!attribute [r] backlog
155
187
  def backlog
156
188
  @thread_pool and @thread_pool.backlog
157
189
  end
158
190
 
191
+ # @!attribute [r] running
159
192
  def running
160
193
  @thread_pool and @thread_pool.spawned
161
194
  end
@@ -168,111 +201,11 @@ module Puma
168
201
  # there are 5 threads sitting idle ready to take
169
202
  # a request. If one request comes in, then the
170
203
  # value would be 4 until it finishes processing.
204
+ # @!attribute [r] pool_capacity
171
205
  def pool_capacity
172
206
  @thread_pool and @thread_pool.pool_capacity
173
207
  end
174
208
 
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
209
  # Runs the server.
277
210
  #
278
211
  # If +background+ is true (the default) then a thread is spun
@@ -286,15 +219,9 @@ module Puma
286
219
 
287
220
  @status = :run
288
221
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
222
  @thread_pool = ThreadPool.new(@min_threads,
296
223
  @max_threads,
297
- IOBuffer) do |client, buffer|
224
+ ::Puma::IOBuffer) do |client, buffer|
298
225
 
299
226
  # Advertise this server into the thread
300
227
  Thread.current[ThreadLocalKey] = self
@@ -302,40 +229,48 @@ module Puma
302
229
  process_now = false
303
230
 
304
231
  begin
305
- if queue_requests
232
+ if @queue_requests
306
233
  process_now = client.eagerly_finish
307
234
  else
308
- client.finish
235
+ @thread_pool.with_force_shutdown do
236
+ client.finish(@first_data_timeout)
237
+ end
309
238
  process_now = true
310
239
  end
311
240
  rescue MiniSSL::SSLError => e
312
- ssl_socket = client.io
313
- addr = ssl_socket.peeraddr.last
314
- cert = ssl_socket.peercert
315
-
241
+ @events.ssl_error e, client.io
316
242
  client.close
317
243
 
318
- @events.ssl_error self, addr, cert, e
319
244
  rescue HttpParserError => e
320
245
  client.write_error(400)
321
246
  client.close
322
247
 
323
- @events.parse_error self, client.env, e
324
- rescue ConnectionError, EOFError
248
+ @events.parse_error e, client
249
+ rescue EOFError => e
325
250
  client.close
251
+
252
+ # Swallow, do not log
253
+ rescue ConnectionError, ThreadPool::ForceShutdown => e
254
+ client.close
255
+
256
+ @events.connection_error e, client
326
257
  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
258
+ process_now ||= @shutdown_mutex.synchronize do
259
+ next true unless @queue_requests
260
+ client.set_timeout @first_data_timeout
261
+ @reactor.add client
262
+ false
263
+ end
264
+ process_client client, buffer if process_now
333
265
  end
266
+
267
+ process_now
334
268
  end
335
269
 
270
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
271
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
272
 
338
- if queue_requests
273
+ if @queue_requests
339
274
  @reactor = Reactor.new self, @thread_pool
340
275
  @reactor.run_in_thread
341
276
  end
@@ -362,6 +297,7 @@ module Puma
362
297
  end
363
298
 
364
299
  def handle_servers
300
+ @check, @notify = Puma::Util.pipe unless @notify
365
301
  begin
366
302
  check = @check
367
303
  sockets = [check] + @binder.ios
@@ -385,51 +321,51 @@ module Puma
385
321
  if sock == check
386
322
  break if handle_check
387
323
  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
324
+ pool.wait_until_not_full
325
+ pool.wait_for_less_busy_worker(
326
+ @options[:wait_for_less_busy_worker].to_f)
327
+
328
+ io = begin
329
+ sock.accept_nonblock
330
+ rescue IO::WaitReadable
331
+ next
412
332
  end
333
+ client = Client.new io, @binder.env(sock)
334
+ if remote_addr_value
335
+ client.peerip = remote_addr_value
336
+ elsif remote_addr_header
337
+ client.remote_addr_header = remote_addr_header
338
+ end
339
+ pool << client
413
340
  end
414
341
  end
415
342
  rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
343
+ @events.unknown_error e, nil, "Listen loop"
417
344
  end
418
345
  end
419
346
 
420
347
  @events.fire :state, @status
421
348
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
349
  if queue_requests
350
+ @shutdown_mutex.synchronize do
351
+ @queue_requests = false
352
+ end
424
353
  @reactor.clear!
425
354
  @reactor.shutdown
426
355
  end
356
+ graceful_shutdown if @status == :stop || @status == :restart
427
357
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
358
+ @events.unknown_error e, nil, "Exception handling servers"
430
359
  ensure
431
- @check.close
360
+ begin
361
+ @check.close unless @check.closed?
362
+ rescue Errno::EBADF, RuntimeError
363
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
364
+ # Errno::EBADF is infrequently raised
365
+ end
432
366
  @notify.close
367
+ @notify = nil
368
+ @check = nil
433
369
  end
434
370
 
435
371
  @events.fire :state, :done
@@ -476,7 +412,6 @@ module Puma
476
412
  close_socket = false
477
413
  return
478
414
  when true
479
- return unless @queue_requests
480
415
  buffer.reset
481
416
 
482
417
  ThreadPool.clean_thread_locals if clean_thread_locals
@@ -493,38 +428,39 @@ module Puma
493
428
  check_for_more_data = false
494
429
  end
495
430
 
496
- unless client.reset(check_for_more_data)
497
- close_socket = false
498
- client.set_timeout @persistent_timeout
499
- @reactor.add client
500
- return
431
+ next_request_ready = @thread_pool.with_force_shutdown do
432
+ client.reset(check_for_more_data)
433
+ end
434
+
435
+ unless next_request_ready
436
+ @shutdown_mutex.synchronize do
437
+ return unless @queue_requests
438
+ close_socket = false
439
+ client.set_timeout @persistent_timeout
440
+ @reactor.add client
441
+ return
442
+ end
501
443
  end
502
444
  end
503
445
  end
504
446
 
505
447
  # The client disconnected while we were reading data
506
- rescue ConnectionError
448
+ rescue ConnectionError, ThreadPool::ForceShutdown
507
449
  # Swallow them. The ensure tries to close +client+ down
508
450
 
509
451
  # SSL handshake error
510
452
  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
-
453
+ lowlevel_error e, client.env
454
+ @events.ssl_error e, client.io
517
455
  close_socket = true
518
456
 
519
- @events.ssl_error self, addr, cert, e
520
-
521
457
  # The client doesn't know HTTP well
522
458
  rescue HttpParserError => e
523
459
  lowlevel_error(e, client.env)
524
460
 
525
461
  client.write_error(400)
526
462
 
527
- @events.parse_error self, client.env, e
463
+ @events.parse_error e, client
528
464
 
529
465
  # Server error
530
466
  rescue StandardError => e
@@ -532,8 +468,7 @@ module Puma
532
468
 
533
469
  client.write_error(500)
534
470
 
535
- @events.unknown_error self, e, "Read"
536
-
471
+ @events.unknown_error e, nil, "Read"
537
472
  ensure
538
473
  buffer.reset
539
474
 
@@ -543,7 +478,7 @@ module Puma
543
478
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
544
479
  # Already closed
545
480
  rescue StandardError => e
546
- @events.unknown_error self, e, "Client"
481
+ @events.unknown_error e, nil, "Client"
547
482
  end
548
483
  end
549
484
  end
@@ -579,7 +514,7 @@ module Puma
579
514
 
580
515
  env[PATH_INFO] = env[REQUEST_PATH]
581
516
 
582
- # From http://www.ietf.org/rfc/rfc3875 :
517
+ # From https://www.ietf.org/rfc/rfc3875 :
583
518
  # "Script authors should be aware that the REMOTE_ADDR and
584
519
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
585
520
  # may not identify the ultimate source of the request.
@@ -626,6 +561,8 @@ module Puma
626
561
  #
627
562
  # Finally, it'll return +true+ on keep-alive connections.
628
563
  def handle_request(req, lines)
564
+ @requests_count +=1
565
+
629
566
  env = req.env
630
567
  client = req.io
631
568
 
@@ -666,7 +603,8 @@ module Puma
666
603
  end
667
604
 
668
605
  fast_write client, "\r\n".freeze
669
- rescue ConnectionError
606
+ rescue ConnectionError => e
607
+ @events.debug_error e
670
608
  # noop, if we lost the socket we just won't send the early hints
671
609
  end
672
610
  }
@@ -694,7 +632,7 @@ module Puma
694
632
  to_add = {}
695
633
  end
696
634
 
697
- to_add[k.gsub(",", "_")] = v
635
+ to_add[k.tr(",", "_")] = v
698
636
  end
699
637
  end
700
638
 
@@ -710,7 +648,9 @@ module Puma
710
648
 
711
649
  begin
712
650
  begin
713
- status, headers, res_body = @app.call(env)
651
+ status, headers, res_body = @thread_pool.with_force_shutdown do
652
+ @app.call(env)
653
+ end
714
654
 
715
655
  return :async if req.hijacked
716
656
 
@@ -724,17 +664,14 @@ module Puma
724
664
  return :async
725
665
  end
726
666
  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"]
667
+ @events.unknown_error e, req, "Rack app"
668
+ @events.log "Detected force shutdown of a thread"
733
669
 
670
+ status, headers, res_body = lowlevel_error(e, env, 503)
734
671
  rescue Exception => e
735
- @events.unknown_error self, e, "Rack app", env
672
+ @events.unknown_error e, req, "Rack app"
736
673
 
737
- status, headers, res_body = lowlevel_error(e, env)
674
+ status, headers, res_body = lowlevel_error(e, env, 500)
738
675
  end
739
676
 
740
677
  content_length = nil
@@ -749,10 +686,10 @@ module Puma
749
686
  line_ending = LINE_END
750
687
  colon = COLON
751
688
 
752
- http_11 = if env[HTTP_VERSION] == HTTP_11
689
+ http_11 = env[HTTP_VERSION] == HTTP_11
690
+ if http_11
753
691
  allow_chunked = true
754
692
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
755
- include_keepalive_header = false
756
693
 
757
694
  # An optimization. The most common response is 200, so we can
758
695
  # reply with the proper 200 status without having to compute
@@ -766,11 +703,9 @@ module Puma
766
703
 
767
704
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
768
705
  end
769
- true
770
706
  else
771
707
  allow_chunked = false
772
708
  keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
773
- include_keepalive_header = keep_alive
774
709
 
775
710
  # Same optimization as above for HTTP/1.1
776
711
  #
@@ -782,9 +717,12 @@ module Puma
782
717
 
783
718
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
784
719
  end
785
- false
786
720
  end
787
721
 
722
+ # regardless of what the client wants, we always close the connection
723
+ # if running without request queueing
724
+ keep_alive &&= @queue_requests
725
+
788
726
  response_hijack = nil
789
727
 
790
728
  headers.each do |k, vs|
@@ -811,10 +749,15 @@ module Puma
811
749
  end
812
750
  end
813
751
 
814
- if include_keepalive_header
815
- lines << CONNECTION_KEEP_ALIVE
816
- elsif http_11 && !keep_alive
817
- lines << CONNECTION_CLOSE
752
+ # HTTP/1.1 & 1.0 assume different defaults:
753
+ # - HTTP 1.0 assumes the connection will be closed if not specified
754
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
755
+ # Only set the header if we're doing something which is not the default
756
+ # for this protocol version
757
+ if http_11
758
+ lines << CONNECTION_CLOSE if !keep_alive
759
+ else
760
+ lines << CONNECTION_KEEP_ALIVE if keep_alive
818
761
  end
819
762
 
820
763
  if no_body
@@ -941,19 +884,21 @@ module Puma
941
884
 
942
885
  # A fallback rack response if +@app+ raises as exception.
943
886
  #
944
- def lowlevel_error(e, env)
887
+ def lowlevel_error(e, env, status=500)
945
888
  if handler = @options[:lowlevel_error_handler]
946
889
  if handler.arity == 1
947
890
  return handler.call(e)
948
- else
891
+ elsif handler.arity == 2
949
892
  return handler.call(e, env)
893
+ else
894
+ return handler.call(e, env, status)
950
895
  end
951
896
  end
952
897
 
953
898
  if @leak_stack_on_error
954
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
899
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
955
900
  else
956
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
901
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
957
902
  end
958
903
  end
959
904
 
@@ -1003,7 +948,7 @@ module Puma
1003
948
 
1004
949
  if @thread_pool
1005
950
  if timeout = @options[:force_shutdown_after]
1006
- @thread_pool.shutdown timeout.to_i
951
+ @thread_pool.shutdown timeout.to_f
1007
952
  else
1008
953
  @thread_pool.shutdown
1009
954
  end
@@ -1011,9 +956,10 @@ module Puma
1011
956
  end
1012
957
 
1013
958
  def notify_safely(message)
959
+ @check, @notify = Puma::Util.pipe unless @notify
1014
960
  begin
1015
961
  @notify << message
1016
- rescue IOError
962
+ rescue IOError, NoMethodError, Errno::EPIPE
1017
963
  # The server, in another thread, is shutting down
1018
964
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1019
965
  rescue RuntimeError => e
@@ -1040,8 +986,9 @@ module Puma
1040
986
  @thread.join if @thread && sync
1041
987
  end
1042
988
 
1043
- def begin_restart
989
+ def begin_restart(sync=false)
1044
990
  notify_safely(RESTART_COMMAND)
991
+ @thread.join if @thread && sync
1045
992
  end
1046
993
 
1047
994
  def fast_write(io, str)
@@ -1065,12 +1012,6 @@ module Puma
1065
1012
  end
1066
1013
  private :fast_write
1067
1014
 
1068
- ThreadLocalKey = :puma_server
1069
-
1070
- def self.current
1071
- Thread.current[ThreadLocalKey]
1072
- end
1073
-
1074
1015
  def shutting_down?
1075
1016
  @status == :stop || @status == :restart
1076
1017
  end
@@ -1079,5 +1020,16 @@ module Puma
1079
1020
  HTTP_INJECTION_REGEX =~ header_value.to_s
1080
1021
  end
1081
1022
  private :possible_header_injection?
1023
+
1024
+ # List of methods invoked by #stats.
1025
+ # @version 5.0.0
1026
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
1027
+
1028
+ # Returns a hash of stats about the running server for reporting purposes.
1029
+ # @version 5.0.0
1030
+ # @!attribute [r] stats
1031
+ def stats
1032
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1033
+ end
1082
1034
  end
1083
1035
  end