puma 3.12.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +36 -0
  3. data/README.md +29 -9
  4. data/docs/architecture.md +1 -0
  5. data/docs/deployment.md +24 -4
  6. data/docs/restart.md +4 -2
  7. data/docs/systemd.md +27 -9
  8. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  9. data/ext/puma_http11/mini_ssl.c +32 -4
  10. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  11. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +11 -4
  12. data/lib/puma/app/status.rb +3 -2
  13. data/lib/puma/binder.rb +19 -10
  14. data/lib/puma/cli.rb +2 -0
  15. data/lib/puma/client.rb +46 -25
  16. data/lib/puma/cluster.rb +40 -14
  17. data/lib/puma/commonlogger.rb +2 -0
  18. data/lib/puma/configuration.rb +4 -1
  19. data/lib/puma/const.rb +8 -2
  20. data/lib/puma/control_cli.rb +21 -9
  21. data/lib/puma/convenient.rb +2 -0
  22. data/lib/puma/daemon_ext.rb +2 -0
  23. data/lib/puma/delegation.rb +2 -0
  24. data/lib/puma/detect.rb +2 -0
  25. data/lib/puma/dsl.rb +57 -5
  26. data/lib/puma/events.rb +2 -0
  27. data/lib/puma/io_buffer.rb +3 -6
  28. data/lib/puma/jruby_restart.rb +2 -0
  29. data/lib/puma/launcher.rb +14 -13
  30. data/lib/puma/minissl.rb +15 -1
  31. data/lib/puma/null_io.rb +2 -0
  32. data/lib/puma/plugin.rb +2 -0
  33. data/lib/puma/rack/builder.rb +2 -1
  34. data/lib/puma/reactor.rb +106 -53
  35. data/lib/puma/runner.rb +3 -1
  36. data/lib/puma/server.rb +27 -24
  37. data/lib/puma/single.rb +4 -2
  38. data/lib/puma/state_file.rb +2 -0
  39. data/lib/puma/tcp_logger.rb +2 -0
  40. data/lib/puma/thread_pool.rb +7 -1
  41. data/lib/puma/util.rb +2 -6
  42. data/lib/rack/handler/puma.rb +3 -0
  43. data/tools/jungle/init.d/puma +5 -5
  44. metadata +19 -8
  45. data/lib/puma/compat.rb +0 -14
  46. data/lib/puma/java_io_buffer.rb +0 -45
  47. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Rack::CommonLogger forwards every request to the given +app+, and
3
5
  # logs a line in the
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/rack/builder'
2
4
  require 'puma/plugin'
3
5
  require 'puma/const'
@@ -184,7 +186,8 @@ module Puma
184
186
  :rackup => DefaultRackup,
185
187
  :logger => STDOUT,
186
188
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
187
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT
189
+ :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
190
+ :raise_exception_on_sigterm => true
188
191
  }
189
192
  end
190
193
 
data/lib/puma/const.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  #encoding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  module Puma
3
5
  class UnsupportedOption < RuntimeError
4
6
  end
@@ -98,8 +100,8 @@ module Puma
98
100
  # too taxing on performance.
99
101
  module Const
100
102
 
101
- PUMA_VERSION = VERSION = "3.12.0".freeze
102
- CODE_NAME = "Llamas in Pajamas".freeze
103
+ PUMA_VERSION = VERSION = "4.0.0".freeze
104
+ CODE_NAME = "4 Fast 4 Furious".freeze
103
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
104
106
 
105
107
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -225,5 +227,9 @@ module Puma
225
227
  HIJACK_IO = "rack.hijack_io".freeze
226
228
 
227
229
  EARLY_HINTS = "rack.early_hints".freeze
230
+
231
+ # Mininum interval to checks worker health
232
+ WORKER_CHECK_INTERVAL = 5
233
+
228
234
  end
229
235
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
- require 'puma/state_file'
3
- require 'puma/const'
4
- require 'puma/detect'
5
- require 'puma/configuration'
4
+ require_relative 'state_file'
5
+ require_relative 'const'
6
+ require_relative 'detect'
7
+ require_relative 'configuration'
6
8
  require 'uri'
7
9
  require 'socket'
8
10
 
@@ -129,7 +131,7 @@ module Puma
129
131
  uri = URI.parse @control_url
130
132
 
131
133
  # create server object by scheme
132
- @server = case uri.scheme
134
+ server = case uri.scheme
133
135
  when "tcp"
134
136
  TCPSocket.new uri.host, uri.port
135
137
  when "unix"
@@ -147,9 +149,9 @@ module Puma
147
149
  url = url + "?token=#{@control_auth_token}"
148
150
  end
149
151
 
150
- @server << "GET #{url} HTTP/1.0\r\n\r\n"
152
+ server << "GET #{url} HTTP/1.0\r\n\r\n"
151
153
 
152
- unless data = @server.read
154
+ unless data = server.read
153
155
  raise "Server closed connection before responding"
154
156
  end
155
157
 
@@ -172,8 +174,8 @@ module Puma
172
174
  message "Command #{@command} sent success"
173
175
  message response.last if @command == "stats" || @command == "gc-stats"
174
176
  end
175
-
176
- @server.close
177
+ ensure
178
+ server.close if server && !server.closed?
177
179
  end
178
180
 
179
181
  def send_signal
@@ -204,6 +206,16 @@ module Puma
204
206
  when "phased-restart"
205
207
  Process.kill "SIGUSR1", @pid
206
208
 
209
+ when "status"
210
+ begin
211
+ Process.kill 0, @pid
212
+ puts "Puma is started"
213
+ rescue Errno::ESRCH
214
+ raise "Puma is not running"
215
+ end
216
+
217
+ return
218
+
207
219
  else
208
220
  return
209
221
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/launcher'
2
4
  require 'puma/configuration'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Process
2
4
 
3
5
  # This overrides the default version because it is broken if it
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  module Delegation
3
5
  def forward(what, who)
data/lib/puma/detect.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  IS_JRUBY = defined?(JRUBY_VERSION)
3
5
 
data/lib/puma/dsl.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+
1
5
  module Puma
2
6
  # The methods that are available for use inside the config file.
3
7
  # These same methods are used in Puma cli and the rack handler
@@ -55,6 +59,14 @@ module Puma
55
59
  @plugins.clear
56
60
  end
57
61
 
62
+ def set_default_host(host)
63
+ @options[:default_host] = host
64
+ end
65
+
66
+ def default_host
67
+ @options[:default_host] || Configuration::DefaultTCPHost
68
+ end
69
+
58
70
  def inject(&blk)
59
71
  instance_eval(&blk)
60
72
  end
@@ -93,7 +105,12 @@ module Puma
93
105
  end
94
106
 
95
107
  if opts[:no_token]
96
- auth_token = :none
108
+ # We need to use 'none' rather than :none because this value will be
109
+ # passed on to an instance of OptionParser, which doesn't support
110
+ # symbols as option values.
111
+ #
112
+ # See: https://github.com/puma/puma/issues/1193#issuecomment-305995488
113
+ auth_token = 'none'
97
114
  else
98
115
  auth_token = opts[:auth_token]
99
116
  auth_token ||= Configuration.random_token
@@ -138,7 +155,7 @@ module Puma
138
155
  # Define the TCP port to bind to. Use +bind+ for more advanced options.
139
156
  #
140
157
  def port(port, host=nil)
141
- host ||= Configuration::DefaultTCPHost
158
+ host ||= default_host
142
159
  bind "tcp://#{host}:#{port}"
143
160
  end
144
161
 
@@ -285,12 +302,15 @@ module Puma
285
302
 
286
303
  def ssl_bind(host, port, opts)
287
304
  verify = opts.fetch(:verify_mode, 'none')
305
+ no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
306
+ ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
288
307
 
289
308
  if defined?(JRUBY_VERSION)
290
309
  keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
291
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}"
310
+ bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
292
311
  else
293
- bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&verify_mode=#{verify}"
312
+ ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
313
+ bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}#{ca_additions}"
294
314
  end
295
315
  end
296
316
 
@@ -365,6 +385,21 @@ module Puma
365
385
 
366
386
  alias_method :after_worker_boot, :after_worker_fork
367
387
 
388
+ # Code to run out-of-band when the worker is idle.
389
+ # These hooks run immediately after a request has finished
390
+ # processing and there are no busy threads on the worker.
391
+ # The worker doesn't accept new requests until this code finishes.
392
+ #
393
+ # This hook is useful for running out-of-band garbage collection
394
+ # or scheduling asynchronous tasks to execute after a response.
395
+ #
396
+ # This can be called multiple times to add hooks.
397
+ #
398
+ def out_of_band(&block)
399
+ @options[:out_of_band] ||= []
400
+ @options[:out_of_band] << block
401
+ end
402
+
368
403
  # The directory to operate out of.
369
404
  def directory(dir)
370
405
  @options[:directory] = dir.to_s
@@ -414,6 +449,16 @@ module Puma
414
449
  @options[:prune_bundler] = answer
415
450
  end
416
451
 
452
+ # In environments where SIGTERM is something expected, instructing
453
+ # puma to shutdown gracefully ( for example in Kubernetes, where
454
+ # rolling restart is guaranteed usually on infrastructure level )
455
+ # SignalException should not be raised for SIGTERM
456
+ #
457
+ # When set to false, if puma process receives SIGTERM, it won't raise SignalException
458
+ def raise_exception_on_sigterm(answer=true)
459
+ @options[:raise_exception_on_sigterm] = answer
460
+ end
461
+
417
462
  # Additional text to display in process listing
418
463
  def tag(string)
419
464
  @options[:tag] = string.to_s
@@ -424,6 +469,13 @@ module Puma
424
469
  # that have not checked in within the given +timeout+.
425
470
  # This mitigates hung processes. Default value is 60 seconds.
426
471
  def worker_timeout(timeout)
472
+ timeout = Integer(timeout)
473
+ min = Const::WORKER_CHECK_INTERVAL
474
+
475
+ if timeout <= min
476
+ raise "The minimum worker_timeout must be greater than the worker reporting interval (#{min})"
477
+ end
478
+
427
479
  @options[:worker_timeout] = Integer(timeout)
428
480
  end
429
481
 
@@ -493,7 +545,7 @@ module Puma
493
545
  when Hash
494
546
  if hdr = val[:header]
495
547
  @options[:remote_address] = :header
496
- @options[:remote_address_header] = "HTTP_" + hdr.upcase.gsub("-", "_")
548
+ @options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
497
549
  else
498
550
  raise "Invalid value for set_remote_address - #{val.inspect}"
499
551
  end
data/lib/puma/events.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/const'
2
4
  require "puma/null_io"
3
5
  require 'stringio'
@@ -1,7 +1,4 @@
1
- require 'puma/detect'
1
+ # frozen_string_literal: true
2
2
 
3
- if Puma.jruby?
4
- require 'puma/java_io_buffer'
5
- else
6
- require 'puma/puma_http11'
7
- end
3
+ require 'puma/detect'
4
+ require 'puma/puma_http11'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ffi'
2
4
 
3
5
  module Puma
data/lib/puma/launcher.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/events'
2
4
  require 'puma/detect'
3
5
 
@@ -63,8 +65,8 @@ module Puma
63
65
 
64
66
  generate_restart_data
65
67
 
66
- if clustered? && (Puma.jruby? || Puma.windows?)
67
- unsupported 'worker mode not supported on JRuby or Windows'
68
+ if clustered? && !Process.respond_to?(:fork)
69
+ unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
68
70
  end
69
71
 
70
72
  if @options[:daemon] && Puma.windows?
@@ -212,6 +214,15 @@ module Puma
212
214
  end
213
215
  end
214
216
 
217
+ def close_binder_listeners
218
+ @binder.listeners.each do |l, io|
219
+ io.close
220
+ uri = URI.parse(l)
221
+ next unless uri.scheme == 'unix'
222
+ File.unlink("#{uri.host}#{uri.path}")
223
+ end
224
+ end
225
+
215
226
  private
216
227
 
217
228
  def reload_worker_directory
@@ -317,16 +328,6 @@ module Puma
317
328
  @options[:prune_bundler] && clustered? && !@options[:preload_app]
318
329
  end
319
330
 
320
- def close_binder_listeners
321
- @binder.listeners.each do |l, io|
322
- io.close
323
- uri = URI.parse(l)
324
- next unless uri.scheme == 'unix'
325
- File.unlink("#{uri.host}#{uri.path}")
326
- end
327
- end
328
-
329
-
330
331
  def generate_restart_data
331
332
  if dir = @options[:directory]
332
333
  @restart_dir = dir
@@ -395,7 +396,7 @@ module Puma
395
396
  Signal.trap "SIGTERM" do
396
397
  graceful_stop
397
398
 
398
- raise SignalException, "SIGTERM"
399
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
399
400
  end
400
401
  rescue Exception
401
402
  log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
data/lib/puma/minissl.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'io/wait'
3
- rescue LoadError
5
+ rescue LoadError
4
6
  end
5
7
 
6
8
  module Puma
@@ -175,6 +177,11 @@ module Puma
175
177
 
176
178
  class Context
177
179
  attr_accessor :verify_mode
180
+ attr_reader :no_tlsv1
181
+
182
+ def initialize
183
+ @no_tlsv1 = false
184
+ end
178
185
 
179
186
  if defined?(JRUBY_VERSION)
180
187
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
@@ -213,11 +220,18 @@ module Puma
213
220
  @ca = ca
214
221
  end
215
222
 
223
+
216
224
  def check
217
225
  raise "Key not configured" unless @key
218
226
  raise "Cert not configured" unless @cert
219
227
  end
220
228
  end
229
+
230
+ def no_tlsv1=(tlsv1)
231
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
232
+ @no_tlsv1 = tlsv1
233
+ end
234
+
221
235
  end
222
236
 
223
237
  VERIFY_NONE = 0
data/lib/puma/null_io.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Provides an IO-like object that always appears to contain no data.
3
5
  # Used as the value for rack.input when the request has no body.
data/lib/puma/plugin.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class UnknownPlugin < RuntimeError; end
3
5
 
@@ -110,7 +110,8 @@ module Puma::Rack
110
110
 
111
111
  has_options = false
112
112
  server.valid_options.each do |name, description|
113
- next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
113
+ next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
114
+
114
115
  info << " -O %-21s %s" % [name, description]
115
116
  has_options = true
116
117
  end
data/lib/puma/reactor.rb CHANGED
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/util'
2
4
  require 'puma/minissl'
3
5
 
6
+ require 'nio'
7
+
4
8
  module Puma
5
9
  # Internal Docs, Not a public interface.
6
10
  #
@@ -16,12 +20,13 @@ module Puma
16
20
  #
17
21
  # ## Reactor Flow
18
22
  #
19
- # A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance.
20
- # The reactor stores the request in an array and calls `IO.select` on the array in a loop.
23
+ # A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance,
24
+ # which stores it in an array and waits for any of the connections to be ready for reading.
21
25
  #
22
- # When the request is written to by the client then the `IO.select` will "wake up" and
26
+ # The waiting/wake up is performed with nio4r, which will use the apropriate backend (libev, Java NIO or
27
+ # just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
23
28
  # return the references to any objects that caused it to "wake". The reactor
24
- # then loops through each of these request objects, and sees if they're complete. If they
29
+ # then loops through each of these request objects, and sees if they're complete. If they
25
30
  # have a full header and body then the reactor passes the request to a thread pool.
26
31
  # Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
27
32
  #
@@ -36,7 +41,7 @@ module Puma
36
41
  # Creates an instance of Puma::Reactor
37
42
  #
38
43
  # The `server` argument is an instance of `Puma::Server`
39
- # this is used to write a response for "low level errors"
44
+ # that is used to write a response for "low level errors"
40
45
  # when there is an exception inside of the reactor.
41
46
  #
42
47
  # The `app_pool` is an instance of `Puma::ThreadPool`.
@@ -47,6 +52,8 @@ module Puma
47
52
  @events = server.events
48
53
  @app_pool = app_pool
49
54
 
55
+ @selector = NIO::Selector.new
56
+
50
57
  @mutex = Mutex.new
51
58
 
52
59
  # Read / Write pipes to wake up internal while loop
@@ -55,24 +62,26 @@ module Puma
55
62
  @sleep_for = DefaultSleepFor
56
63
  @timeouts = []
57
64
 
58
- @sockets = [@ready]
65
+ mon = @selector.register(@ready, :r)
66
+ mon.value = @ready
67
+
68
+ @monitors = [mon]
59
69
  end
60
70
 
61
71
  private
62
72
 
63
-
64
73
  # Until a request is added via the `add` method this method will internally
65
74
  # loop, waiting on the `sockets` array objects. The only object in this
66
75
  # array at first is the `@ready` IO object, which is the read end of a pipe
67
76
  # connected to `@trigger` object. When `@trigger` is written to, then the loop
68
- # will break on `IO.select` and return an array.
77
+ # will break on `NIO::Selector#select` and return an array.
69
78
  #
70
79
  # ## When a request is added:
71
80
  #
72
81
  # When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
73
82
  # Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
74
83
  #
75
- # When that happens, the internal loop stops blocking at `IO.select` and returns a reference
84
+ # When that happens, the internal loop stops blocking at `NIO::Selector#select` and returns a reference
76
85
  # to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
77
86
  # When `@trigger` is written-to, the loop "wakes" and the `ready`
78
87
  # variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
@@ -88,11 +97,11 @@ module Puma
88
97
  # to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
89
98
  #
90
99
  # Since the `Puma::Client` in this example has data that has not been read yet,
91
- # the `IO.select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
100
+ # the `NIO::Selector#select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
92
101
  # `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
93
102
  #
94
103
  # Each element in the first entry is iterated over. The `Puma::Client` object is not
95
- # the `@ready` pipe, so the reactor checks to see if it has the fully header and body with
104
+ # the `@ready` pipe, so the reactor checks to see if it has the full header and body with
96
105
  # the `Puma::Client#try_to_finish` method. If the full request has been sent,
97
106
  # then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
98
107
  # can pick up the request and begin to execute application logic. This is done
@@ -100,56 +109,93 @@ module Puma
100
109
  #
101
110
  # If the request body is not present then nothing will happen, and the loop will iterate
102
111
  # again. When the client sends more data to the socket the `Puma::Client` object will
103
- # wake up the `IO.select` and it can again be checked to see if it's ready to be
112
+ # wake up the `NIO::Selector#select` and it can again be checked to see if it's ready to be
104
113
  # passed to the thread pool.
105
114
  #
106
115
  # ## Time Out Case
107
116
  #
108
- # In addition to being woken via a write to one of the sockets the `IO.select` will
117
+ # In addition to being woken via a write to one of the sockets the `NIO::Selector#select` will
109
118
  # periodically "time out" of the sleep. One of the functions of this is to check for
110
119
  # any requests that have "timed out". At the end of the loop it's checked to see if
111
- # the first element in the `@timeout` array has exceed it's allowed time. If so,
112
- # the client object is removed from the timeout aray, a 408 response is written.
113
- # Then it's connection is closed, and the object is removed from the `sockets` array
120
+ # the first element in the `@timeout` array has exceed its allowed time. If so,
121
+ # the client object is removed from the timeout array, a 408 response is written.
122
+ # Then its connection is closed, and the object is removed from the `sockets` array
114
123
  # that watches for new data.
115
124
  #
116
125
  # This behavior loops until all the objects that have timed out have been removed.
117
126
  #
118
- # Once all the timeouts have been processed, the next duration of the `IO.select` sleep
127
+ # Once all the timeouts have been processed, the next duration of the `NIO::Selector#select` sleep
119
128
  # will be set to be equal to the amount of time it will take for the next timeout to occur.
120
129
  # This calculation happens in `calculate_sleep`.
121
130
  def run_internal
122
- sockets = @sockets
131
+ monitors = @monitors
132
+ selector = @selector
123
133
 
124
134
  while true
125
135
  begin
126
- ready = IO.select sockets, nil, nil, @sleep_for
136
+ ready = selector.select @sleep_for
127
137
  rescue IOError => e
128
138
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
129
- if sockets.any? { |socket| socket.closed? }
139
+ if monitors.any? { |mon| mon.value.closed? }
130
140
  STDERR.puts "Error in select: #{e.message} (#{e.class})"
131
141
  STDERR.puts e.backtrace
132
- sockets = sockets.reject { |socket| socket.closed? }
142
+
143
+ monitors.reject! do |mon|
144
+ if mon.value.closed?
145
+ selector.deregister mon.value
146
+ true
147
+ end
148
+ end
149
+
133
150
  retry
134
151
  else
135
152
  raise
136
153
  end
137
154
  end
138
155
 
139
- if ready and reads = ready[0]
140
- reads.each do |c|
141
- if c == @ready
156
+ if ready
157
+ ready.each do |mon|
158
+ if mon.value == @ready
142
159
  @mutex.synchronize do
143
160
  case @ready.read(1)
144
161
  when "*"
145
- sockets += @input
162
+ @input.each do |c|
163
+ mon = nil
164
+ begin
165
+ begin
166
+ mon = selector.register(c, :r)
167
+ rescue ArgumentError
168
+ # There is a bug where we seem to be registering an already registered
169
+ # client. This code deals with this situation but I wish we didn't have to.
170
+ monitors.delete_if { |submon| submon.value.to_io == c.to_io }
171
+ selector.deregister(c)
172
+ mon = selector.register(c, :r)
173
+ end
174
+ rescue IOError
175
+ # Means that the io is closed, so we should ignore this request
176
+ # entirely
177
+ else
178
+ mon.value = c
179
+ @timeouts << mon if c.timeout_at
180
+ monitors << mon
181
+ end
182
+ end
146
183
  @input.clear
184
+
185
+ @timeouts.sort! { |a,b| a.value.timeout_at <=> b.value.timeout_at }
186
+ calculate_sleep
147
187
  when "c"
148
- sockets.delete_if do |s|
149
- if s == @ready
188
+ monitors.reject! do |submon|
189
+ if submon.value == @ready
150
190
  false
151
191
  else
152
- s.close
192
+ submon.value.close
193
+ begin
194
+ selector.deregister submon.value
195
+ rescue IOError
196
+ # nio4r on jruby seems to throw an IOError here if the IO is closed, so
197
+ # we need to swallow it.
198
+ end
153
199
  true
154
200
  end
155
201
  end
@@ -158,19 +204,21 @@ module Puma
158
204
  end
159
205
  end
160
206
  else
207
+ c = mon.value
208
+
161
209
  # We have to be sure to remove it from the timeout
162
210
  # list or we'll accidentally close the socket when
163
211
  # it's in use!
164
212
  if c.timeout_at
165
213
  @mutex.synchronize do
166
- @timeouts.delete c
214
+ @timeouts.delete mon
167
215
  end
168
216
  end
169
217
 
170
218
  begin
171
219
  if c.try_to_finish
172
220
  @app_pool << c
173
- sockets.delete c
221
+ clear_monitor mon
174
222
  end
175
223
 
176
224
  # Don't report these to the lowlevel_error handler, otherwise
@@ -180,18 +228,23 @@ module Puma
180
228
  c.write_500
181
229
  c.close
182
230
 
183
- sockets.delete c
231
+ clear_monitor mon
184
232
 
185
233
  # SSL handshake failure
186
234
  rescue MiniSSL::SSLError => e
187
235
  @server.lowlevel_error(e, c.env)
188
236
 
189
237
  ssl_socket = c.io
190
- addr = ssl_socket.peeraddr.last
238
+ begin
239
+ addr = ssl_socket.peeraddr.last
240
+ rescue IOError
241
+ addr = "<unknown>"
242
+ end
243
+
191
244
  cert = ssl_socket.peercert
192
245
 
193
246
  c.close
194
- sockets.delete c
247
+ clear_monitor mon
195
248
 
196
249
  @events.ssl_error @server, addr, cert, e
197
250
 
@@ -202,7 +255,7 @@ module Puma
202
255
  c.write_400
203
256
  c.close
204
257
 
205
- sockets.delete c
258
+ clear_monitor mon
206
259
 
207
260
  @events.parse_error @server, c.env, e
208
261
  rescue StandardError => e
@@ -211,7 +264,7 @@ module Puma
211
264
  c.write_500
212
265
  c.close
213
266
 
214
- sockets.delete c
267
+ clear_monitor mon
215
268
  end
216
269
  end
217
270
  end
@@ -221,11 +274,13 @@ module Puma
221
274
  @mutex.synchronize do
222
275
  now = Time.now
223
276
 
224
- while @timeouts.first.timeout_at < now
225
- c = @timeouts.shift
277
+ while @timeouts.first.value.timeout_at < now
278
+ mon = @timeouts.shift
279
+ c = mon.value
226
280
  c.write_408 if c.in_data_phase
227
281
  c.close
228
- sockets.delete c
282
+
283
+ clear_monitor mon
229
284
 
230
285
  break if @timeouts.empty?
231
286
  end
@@ -236,6 +291,11 @@ module Puma
236
291
  end
237
292
  end
238
293
 
294
+ def clear_monitor(mon)
295
+ @selector.deregister mon.value
296
+ @monitors.delete mon
297
+ end
298
+
239
299
  public
240
300
 
241
301
  def run
@@ -260,7 +320,7 @@ module Puma
260
320
  end
261
321
  end
262
322
 
263
- # The `calculate_sleep` sets the value that the `IO.select` will
323
+ # The `calculate_sleep` sets the value that the `NIO::Selector#select` will
264
324
  # sleep for in the main reactor loop when no sockets are being written to.
265
325
  #
266
326
  # The values kept in `@timeouts` are sorted so that the first timeout
@@ -274,7 +334,7 @@ module Puma
274
334
  if @timeouts.empty?
275
335
  @sleep_for = DefaultSleepFor
276
336
  else
277
- diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
337
+ diff = @timeouts.first.value.timeout_at.to_f - Time.now.to_f
278
338
 
279
339
  if diff < 0.0
280
340
  @sleep_for = 0
@@ -291,18 +351,18 @@ module Puma
291
351
  # object.
292
352
  #
293
353
  # The main body of the reactor loop is in `run_internal` and it
294
- # will sleep on `IO.select`. When a new connection is added to the
295
- # reactor it cannot be added directly to the `sockets` aray, because
296
- # the `IO.select` will not be watching for it yet.
354
+ # will sleep on `NIO::Selector#select`. When a new connection is added to the
355
+ # reactor it cannot be added directly to the `sockets` array, because
356
+ # the `NIO::Selector#select` will not be watching for it yet.
297
357
  #
298
- # Instead what needs to happen is that `IO.select` needs to be woken up,
358
+ # Instead what needs to happen is that `NIO::Selector#select` needs to be woken up,
299
359
  # the contents of `@input` added to the `sockets` array, and then
300
- # another call to `IO.select` needs to happen. Since the `Puma::Client`
360
+ # another call to `NIO::Selector#select` needs to happen. Since the `Puma::Client`
301
361
  # object can be read immediately, it does not block, but instead returns
302
362
  # right away.
303
363
  #
304
364
  # This behavior is accomplished by writing to `@trigger` which wakes up
305
- # the `IO.select` and then there is logic to detect the value of `*`,
365
+ # the `NIO::Selector#select` and then there is logic to detect the value of `*`,
306
366
  # pull the contents from `@input` and add them to the sockets array.
307
367
  #
308
368
  # If the object passed in has a timeout value in `timeout_at` then
@@ -313,13 +373,6 @@ module Puma
313
373
  @mutex.synchronize do
314
374
  @input << c
315
375
  @trigger << "*"
316
-
317
- if c.timeout_at
318
- @timeouts << c
319
- @timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
320
-
321
- calculate_sleep
322
- end
323
376
  end
324
377
  end
325
378