puma 3.12.2 → 4.2.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +106 -6
  3. data/README.md +91 -43
  4. data/docs/architecture.md +1 -0
  5. data/docs/deployment.md +24 -4
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/plugins.md +20 -10
  10. data/docs/restart.md +4 -2
  11. data/docs/systemd.md +27 -9
  12. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  13. data/ext/puma_http11/extconf.rb +8 -0
  14. data/ext/puma_http11/http11_parser.c +37 -62
  15. data/ext/puma_http11/http11_parser_common.rl +3 -3
  16. data/ext/puma_http11/mini_ssl.c +78 -8
  17. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
  19. data/lib/puma.rb +8 -0
  20. data/lib/puma/accept_nonblock.rb +7 -1
  21. data/lib/puma/app/status.rb +35 -29
  22. data/lib/puma/binder.rb +39 -5
  23. data/lib/puma/cli.rb +4 -0
  24. data/lib/puma/client.rb +221 -199
  25. data/lib/puma/cluster.rb +53 -30
  26. data/lib/puma/configuration.rb +4 -3
  27. data/lib/puma/const.rb +22 -25
  28. data/lib/puma/control_cli.rb +21 -4
  29. data/lib/puma/dsl.rb +297 -75
  30. data/lib/puma/events.rb +4 -1
  31. data/lib/puma/io_buffer.rb +1 -6
  32. data/lib/puma/launcher.rb +95 -53
  33. data/lib/puma/minissl.rb +35 -17
  34. data/lib/puma/plugin.rb +5 -2
  35. data/lib/puma/plugin/tmp_restart.rb +2 -0
  36. data/lib/puma/rack/builder.rb +2 -0
  37. data/lib/puma/rack/urlmap.rb +2 -0
  38. data/lib/puma/rack_default.rb +2 -0
  39. data/lib/puma/reactor.rb +109 -57
  40. data/lib/puma/runner.rb +4 -3
  41. data/lib/puma/server.rb +59 -62
  42. data/lib/puma/single.rb +3 -3
  43. data/lib/puma/thread_pool.rb +14 -32
  44. data/lib/puma/util.rb +1 -6
  45. data/lib/rack/handler/puma.rb +3 -3
  46. data/tools/docker/Dockerfile +16 -0
  47. data/tools/jungle/init.d/puma +6 -6
  48. data/tools/trickletest.rb +0 -1
  49. metadata +20 -8
  50. data/lib/puma/compat.rb +0 -14
  51. data/lib/puma/daemon_ext.rb +0 -33
  52. data/lib/puma/delegation.rb +0 -13
  53. data/lib/puma/java_io_buffer.rb +0 -47
  54. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
@@ -93,7 +93,10 @@ module Puma
93
93
  # parsing exception.
94
94
  #
95
95
  def parse_error(server, env, error)
96
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n"
96
+ @stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
97
+ "(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
98
+ "#{error.inspect}" \
99
+ "\n---\n"
97
100
  end
98
101
 
99
102
  # An SSL error has occurred.
@@ -1,9 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'puma/detect'
4
-
5
- if Puma.jruby?
6
- require 'puma/java_io_buffer'
7
- else
8
- require 'puma/puma_http11'
9
- end
4
+ require 'puma/puma_http11'
@@ -2,12 +2,9 @@
2
2
 
3
3
  require 'puma/events'
4
4
  require 'puma/detect'
5
-
6
5
  require 'puma/cluster'
7
6
  require 'puma/single'
8
-
9
7
  require 'puma/const'
10
-
11
8
  require 'puma/binder'
12
9
 
13
10
  module Puma
@@ -63,6 +60,9 @@ module Puma
63
60
  @options = @config.options
64
61
  @config.clamp
65
62
 
63
+ @events.formatter = Events::PidFormatter.new if clustered?
64
+ @events.formatter = options[:log_formatter] if @options[:log_formatter]
65
+
66
66
  generate_restart_data
67
67
 
68
68
  if clustered? && !Process.respond_to?(:fork)
@@ -81,7 +81,6 @@ module Puma
81
81
  set_rack_environment
82
82
 
83
83
  if clustered?
84
- @events.formatter = Events::PidFormatter.new
85
84
  @options[:logger] = @events
86
85
 
87
86
  @runner = Cluster.new(self, @events)
@@ -124,19 +123,6 @@ module Puma
124
123
  File.unlink(path) if path && File.exist?(path)
125
124
  end
126
125
 
127
- # If configured, write the pid of the current process out
128
- # to a file.
129
- def write_pid
130
- path = @options[:pidfile]
131
- return unless path
132
-
133
- File.open(path, 'w') { |f| f.puts Process.pid }
134
- cur = Process.pid
135
- at_exit do
136
- delete_pidfile if cur == Process.pid
137
- end
138
- end
139
-
140
126
  # Begin async shutdown of the server
141
127
  def halt
142
128
  @status = :halt
@@ -198,6 +184,7 @@ module Puma
198
184
  when :exit
199
185
  # nothing
200
186
  end
187
+ @binder.close_unix_paths
201
188
  end
202
189
 
203
190
  # Return which tcp port the launcher is using, if it's using TCP
@@ -214,8 +201,25 @@ module Puma
214
201
  end
215
202
  end
216
203
 
204
+ def close_binder_listeners
205
+ @binder.close_listeners
206
+ end
207
+
217
208
  private
218
209
 
210
+ # If configured, write the pid of the current process out
211
+ # to a file.
212
+ def write_pid
213
+ path = @options[:pidfile]
214
+ return unless path
215
+
216
+ File.open(path, 'w') { |f| f.puts Process.pid }
217
+ cur = Process.pid
218
+ at_exit do
219
+ delete_pidfile if cur == Process.pid
220
+ end
221
+ end
222
+
219
223
  def reload_worker_directory
220
224
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
221
225
  end
@@ -235,48 +239,71 @@ module Puma
235
239
  Dir.chdir(@restart_dir)
236
240
  Kernel.exec(*argv)
237
241
  else
238
- redirects = {:close_others => true}
239
- @binder.listeners.each_with_index do |(l, io), i|
240
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
241
- redirects[io.to_i] = io.to_i
242
- end
243
-
244
242
  argv = restart_args
245
243
  Dir.chdir(@restart_dir)
246
- argv += [redirects] if RUBY_VERSION >= '1.9'
244
+ argv += [@binder.redirects_for_restart]
247
245
  Kernel.exec(*argv)
248
246
  end
249
247
  end
250
248
 
251
- def prune_bundler
252
- return unless defined?(Bundler)
253
- puma = Bundler.rubygems.loaded_specs("puma")
254
- dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
249
+ def dependencies_and_files_to_require_after_prune
250
+ puma = spec_for_gem("puma")
251
+
252
+ deps = puma.runtime_dependencies.map do |d|
253
+ "#{d.name}:#{spec_for_gem(d.name).version}"
254
+ end
255
+
256
+ [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
257
+ end
258
+
259
+ def extra_runtime_deps_directories
260
+ Array(@options[:extra_runtime_dependencies]).map do |d_name|
261
+ if (spec = spec_for_gem(d_name))
262
+ require_paths_for_gem(spec)
263
+ else
264
+ log "* Could not load extra dependency: #{d_name}"
265
+ nil
266
+ end
267
+ end.flatten.compact
268
+ end
269
+
270
+ def puma_wild_location
271
+ puma = spec_for_gem("puma")
272
+ dirs = require_paths_for_gem(puma)
255
273
  puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
274
+ File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
275
+ end
256
276
 
257
- unless puma_lib_dir
277
+ def prune_bundler
278
+ return unless defined?(Bundler)
279
+ require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
+ unless puma_wild_location
258
281
  log "! Unable to prune Bundler environment, continuing"
259
282
  return
260
283
  end
261
284
 
262
- deps = puma.runtime_dependencies.map do |d|
263
- spec = Bundler.rubygems.loaded_specs(d.name)
264
- "#{d.name}:#{spec.version.to_s}"
265
- end
285
+ deps, dirs = dependencies_and_files_to_require_after_prune
266
286
 
267
287
  log '* Pruning Bundler environment'
268
288
  home = ENV['GEM_HOME']
269
289
  Bundler.with_clean_env do
270
290
  ENV['GEM_HOME'] = home
271
291
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
272
- wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
273
- args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
292
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
274
293
  # Ruby 2.0+ defaults to true which breaks socket activation
275
- args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
294
+ args += [{:close_others => false}]
276
295
  Kernel.exec(*args)
277
296
  end
278
297
  end
279
298
 
299
+ def spec_for_gem(gem_name)
300
+ Bundler.rubygems.loaded_specs(gem_name)
301
+ end
302
+
303
+ def require_paths_for_gem(gem_spec)
304
+ gem_spec.full_require_paths
305
+ end
306
+
280
307
  def log(str)
281
308
  @events.log str
282
309
  end
@@ -296,6 +323,21 @@ module Puma
296
323
  log "- Goodbye!"
297
324
  end
298
325
 
326
+ def log_thread_status
327
+ Thread.list.each do |thread|
328
+ log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
329
+ logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
330
+ logstr += " #{thread.name}" if thread.respond_to?(:name)
331
+ log logstr
332
+
333
+ if thread.backtrace
334
+ log thread.backtrace.join("\n")
335
+ else
336
+ log "<no backtrace available>"
337
+ end
338
+ end
339
+ end
340
+
299
341
  def set_process_title
300
342
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
301
343
  end
@@ -319,16 +361,6 @@ module Puma
319
361
  @options[:prune_bundler] && clustered? && !@options[:preload_app]
320
362
  end
321
363
 
322
- def close_binder_listeners
323
- @binder.listeners.each do |l, io|
324
- io.close
325
- uri = URI.parse(l)
326
- next unless uri.scheme == 'unix'
327
- File.unlink("#{uri.host}#{uri.path}")
328
- end
329
- end
330
-
331
-
332
364
  def generate_restart_data
333
365
  if dir = @options[:directory]
334
366
  @restart_dir = dir
@@ -397,7 +429,7 @@ module Puma
397
429
  Signal.trap "SIGTERM" do
398
430
  graceful_stop
399
431
 
400
- raise SignalException, "SIGTERM"
432
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
401
433
  end
402
434
  rescue Exception
403
435
  log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
@@ -405,12 +437,6 @@ module Puma
405
437
 
406
438
  begin
407
439
  Signal.trap "SIGINT" do
408
- if Puma.jruby?
409
- @status = :exit
410
- graceful_stop
411
- exit
412
- end
413
-
414
440
  stop
415
441
  end
416
442
  rescue Exception
@@ -428,6 +454,22 @@ module Puma
428
454
  rescue Exception
429
455
  log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
430
456
  end
457
+
458
+ begin
459
+ Signal.trap "SIGINFO" do
460
+ log_thread_status
461
+ end
462
+ rescue Exception
463
+ # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
464
+ # to see this constantly on Linux.
465
+ end
466
+ end
467
+
468
+ def require_rubygems_min_version!(min_version, feature)
469
+ return if min_version <= Gem::Version.new(Gem::VERSION)
470
+
471
+ raise "#{feature} is not supported on your version of RubyGems. " \
472
+ "You must have RubyGems #{min_version}+ to use this feature."
431
473
  end
432
474
  end
433
475
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  begin
4
4
  require 'io/wait'
5
- rescue LoadError
5
+ rescue LoadError
6
6
  end
7
7
 
8
8
  module Puma
@@ -54,22 +54,21 @@ module Puma
54
54
  output = engine_read_all
55
55
  return output if output
56
56
 
57
- begin
58
- data = @socket.read_nonblock(size, exception: false)
59
- if data == :wait_readable || data == :wait_writable
60
- if @socket.to_io.respond_to?(data)
61
- @socket.to_io.__send__(data)
62
- elsif data == :wait_readable
63
- IO.select([@socket.to_io])
64
- else
65
- IO.select(nil, [@socket.to_io])
66
- end
67
- elsif !data
68
- return nil
69
- else
70
- break
71
- end
72
- end while true
57
+ data = @socket.read_nonblock(size, exception: false)
58
+ if data == :wait_readable || data == :wait_writable
59
+ # It would make more sense to let @socket.read_nonblock raise
60
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
61
+ # I don't have a Windows machine to debug this so I can't explain
62
+ # exactly whats happening in that OS. Please let me know if you
63
+ # find out!
64
+ #
65
+ # In the meantime, we can emulate the correct behavior by
66
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
67
+ # ourselves.
68
+ raise IO::EAGAINWaitReadable
69
+ elsif data.nil?
70
+ return nil
71
+ end
73
72
 
74
73
  @engine.inject(data)
75
74
  output = engine_read_all
@@ -177,6 +176,12 @@ module Puma
177
176
 
178
177
  class Context
179
178
  attr_accessor :verify_mode
179
+ attr_reader :no_tlsv1, :no_tlsv1_1
180
+
181
+ def initialize
182
+ @no_tlsv1 = false
183
+ @no_tlsv1_1 = false
184
+ end
180
185
 
181
186
  if defined?(JRUBY_VERSION)
182
187
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
@@ -220,6 +225,19 @@ module Puma
220
225
  raise "Cert not configured" unless @cert
221
226
  end
222
227
  end
228
+
229
+ # disables TLSv1
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
+
235
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
236
+ def no_tlsv1_1=(tlsv1_1)
237
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
238
+ @no_tlsv1_1 = tlsv1_1
239
+ end
240
+
223
241
  end
224
242
 
225
243
  VERIFY_NONE = 0
@@ -62,8 +62,11 @@ module Puma
62
62
  end
63
63
 
64
64
  def fire_background
65
- @background.each do |b|
66
- Thread.new(&b)
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
67
70
  end
68
71
  end
69
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/plugin'
2
4
 
3
5
  Puma::Plugin.create do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  end
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma::Rack
2
4
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
5
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/handler/puma'
2
4
 
3
5
  module Rack::Handler
@@ -3,6 +3,8 @@
3
3
  require 'puma/util'
4
4
  require 'puma/minissl'
5
5
 
6
+ require 'nio'
7
+
6
8
  module Puma
7
9
  # Internal Docs, Not a public interface.
8
10
  #
@@ -18,12 +20,13 @@ module Puma
18
20
  #
19
21
  # ## Reactor Flow
20
22
  #
21
- # A request comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance.
22
- # 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.
23
25
  #
24
- # 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 appropriate backend (libev, Java NIO or
27
+ # just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
25
28
  # return the references to any objects that caused it to "wake". The reactor
26
- # 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
27
30
  # have a full header and body then the reactor passes the request to a thread pool.
28
31
  # Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
29
32
  #
@@ -38,7 +41,7 @@ module Puma
38
41
  # Creates an instance of Puma::Reactor
39
42
  #
40
43
  # The `server` argument is an instance of `Puma::Server`
41
- # this is used to write a response for "low level errors"
44
+ # that is used to write a response for "low level errors"
42
45
  # when there is an exception inside of the reactor.
43
46
  #
44
47
  # The `app_pool` is an instance of `Puma::ThreadPool`.
@@ -49,6 +52,8 @@ module Puma
49
52
  @events = server.events
50
53
  @app_pool = app_pool
51
54
 
55
+ @selector = NIO::Selector.new
56
+
52
57
  @mutex = Mutex.new
53
58
 
54
59
  # Read / Write pipes to wake up internal while loop
@@ -57,24 +62,26 @@ module Puma
57
62
  @sleep_for = DefaultSleepFor
58
63
  @timeouts = []
59
64
 
60
- @sockets = [@ready]
65
+ mon = @selector.register(@ready, :r)
66
+ mon.value = @ready
67
+
68
+ @monitors = [mon]
61
69
  end
62
70
 
63
71
  private
64
72
 
65
-
66
73
  # Until a request is added via the `add` method this method will internally
67
74
  # loop, waiting on the `sockets` array objects. The only object in this
68
75
  # array at first is the `@ready` IO object, which is the read end of a pipe
69
76
  # connected to `@trigger` object. When `@trigger` is written to, then the loop
70
- # will break on `IO.select` and return an array.
77
+ # will break on `NIO::Selector#select` and return an array.
71
78
  #
72
79
  # ## When a request is added:
73
80
  #
74
81
  # When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
75
82
  # Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
76
83
  #
77
- # 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
78
85
  # to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
79
86
  # When `@trigger` is written-to, the loop "wakes" and the `ready`
80
87
  # variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
@@ -90,11 +97,11 @@ module Puma
90
97
  # to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
91
98
  #
92
99
  # Since the `Puma::Client` in this example has data that has not been read yet,
93
- # 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
94
101
  # `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
95
102
  #
96
103
  # Each element in the first entry is iterated over. The `Puma::Client` object is not
97
- # 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
98
105
  # the `Puma::Client#try_to_finish` method. If the full request has been sent,
99
106
  # then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
100
107
  # can pick up the request and begin to execute application logic. This is done
@@ -102,56 +109,93 @@ module Puma
102
109
  #
103
110
  # If the request body is not present then nothing will happen, and the loop will iterate
104
111
  # again. When the client sends more data to the socket the `Puma::Client` object will
105
- # 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
106
113
  # passed to the thread pool.
107
114
  #
108
115
  # ## Time Out Case
109
116
  #
110
- # 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
111
118
  # periodically "time out" of the sleep. One of the functions of this is to check for
112
119
  # any requests that have "timed out". At the end of the loop it's checked to see if
113
- # the first element in the `@timeout` array has exceed it's allowed time. If so,
114
- # the client object is removed from the timeout aray, a 408 response is written.
115
- # 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
116
123
  # that watches for new data.
117
124
  #
118
125
  # This behavior loops until all the objects that have timed out have been removed.
119
126
  #
120
- # 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
121
128
  # will be set to be equal to the amount of time it will take for the next timeout to occur.
122
129
  # This calculation happens in `calculate_sleep`.
123
130
  def run_internal
124
- sockets = @sockets
131
+ monitors = @monitors
132
+ selector = @selector
125
133
 
126
134
  while true
127
135
  begin
128
- ready = IO.select sockets, nil, nil, @sleep_for
136
+ ready = selector.select @sleep_for
129
137
  rescue IOError => e
130
138
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
131
- if sockets.any? { |socket| socket.closed? }
139
+ if monitors.any? { |mon| mon.value.closed? }
132
140
  STDERR.puts "Error in select: #{e.message} (#{e.class})"
133
141
  STDERR.puts e.backtrace
134
- 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
+
135
150
  retry
136
151
  else
137
152
  raise
138
153
  end
139
154
  end
140
155
 
141
- if ready and reads = ready[0]
142
- reads.each do |c|
143
- if c == @ready
156
+ if ready
157
+ ready.each do |mon|
158
+ if mon.value == @ready
144
159
  @mutex.synchronize do
145
160
  case @ready.read(1)
146
161
  when "*"
147
- 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
148
183
  @input.clear
184
+
185
+ @timeouts.sort! { |a,b| a.value.timeout_at <=> b.value.timeout_at }
186
+ calculate_sleep
149
187
  when "c"
150
- sockets.delete_if do |s|
151
- if s == @ready
188
+ monitors.reject! do |submon|
189
+ if submon.value == @ready
152
190
  false
153
191
  else
154
- 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
155
199
  true
156
200
  end
157
201
  end
@@ -160,40 +204,47 @@ module Puma
160
204
  end
161
205
  end
162
206
  else
207
+ c = mon.value
208
+
163
209
  # We have to be sure to remove it from the timeout
164
210
  # list or we'll accidentally close the socket when
165
211
  # it's in use!
166
212
  if c.timeout_at
167
213
  @mutex.synchronize do
168
- @timeouts.delete c
214
+ @timeouts.delete mon
169
215
  end
170
216
  end
171
217
 
172
218
  begin
173
219
  if c.try_to_finish
174
220
  @app_pool << c
175
- sockets.delete c
221
+ clear_monitor mon
176
222
  end
177
223
 
178
224
  # Don't report these to the lowlevel_error handler, otherwise
179
225
  # will be flooding them with errors when persistent connections
180
226
  # are closed.
181
227
  rescue ConnectionError
182
- c.write_500
228
+ c.write_error(500)
183
229
  c.close
184
230
 
185
- sockets.delete c
231
+ clear_monitor mon
186
232
 
187
233
  # SSL handshake failure
188
234
  rescue MiniSSL::SSLError => e
189
235
  @server.lowlevel_error(e, c.env)
190
236
 
191
237
  ssl_socket = c.io
192
- addr = ssl_socket.peeraddr.last
238
+ begin
239
+ addr = ssl_socket.peeraddr.last
240
+ rescue IOError
241
+ addr = "<unknown>"
242
+ end
243
+
193
244
  cert = ssl_socket.peercert
194
245
 
195
246
  c.close
196
- sockets.delete c
247
+ clear_monitor mon
197
248
 
198
249
  @events.ssl_error @server, addr, cert, e
199
250
 
@@ -201,19 +252,19 @@ module Puma
201
252
  rescue HttpParserError => e
202
253
  @server.lowlevel_error(e, c.env)
203
254
 
204
- c.write_400
255
+ c.write_error(400)
205
256
  c.close
206
257
 
207
- sockets.delete c
258
+ clear_monitor mon
208
259
 
209
260
  @events.parse_error @server, c.env, e
210
261
  rescue StandardError => e
211
262
  @server.lowlevel_error(e, c.env)
212
263
 
213
- c.write_500
264
+ c.write_error(500)
214
265
  c.close
215
266
 
216
- sockets.delete c
267
+ clear_monitor mon
217
268
  end
218
269
  end
219
270
  end
@@ -223,11 +274,13 @@ module Puma
223
274
  @mutex.synchronize do
224
275
  now = Time.now
225
276
 
226
- while @timeouts.first.timeout_at < now
227
- c = @timeouts.shift
228
- c.write_408 if c.in_data_phase
277
+ while @timeouts.first.value.timeout_at < now
278
+ mon = @timeouts.shift
279
+ c = mon.value
280
+ c.write_error(408) if c.in_data_phase
229
281
  c.close
230
- sockets.delete c
282
+
283
+ clear_monitor mon
231
284
 
232
285
  break if @timeouts.empty?
233
286
  end
@@ -238,6 +291,11 @@ module Puma
238
291
  end
239
292
  end
240
293
 
294
+ def clear_monitor(mon)
295
+ @selector.deregister mon.value
296
+ @monitors.delete mon
297
+ end
298
+
241
299
  public
242
300
 
243
301
  def run
@@ -249,6 +307,7 @@ module Puma
249
307
 
250
308
  def run_in_thread
251
309
  @thread = Thread.new do
310
+ Puma.set_thread_name "reactor"
252
311
  begin
253
312
  run_internal
254
313
  rescue StandardError => e
@@ -262,7 +321,7 @@ module Puma
262
321
  end
263
322
  end
264
323
 
265
- # The `calculate_sleep` sets the value that the `IO.select` will
324
+ # The `calculate_sleep` sets the value that the `NIO::Selector#select` will
266
325
  # sleep for in the main reactor loop when no sockets are being written to.
267
326
  #
268
327
  # The values kept in `@timeouts` are sorted so that the first timeout
@@ -276,7 +335,7 @@ module Puma
276
335
  if @timeouts.empty?
277
336
  @sleep_for = DefaultSleepFor
278
337
  else
279
- diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
338
+ diff = @timeouts.first.value.timeout_at.to_f - Time.now.to_f
280
339
 
281
340
  if diff < 0.0
282
341
  @sleep_for = 0
@@ -293,18 +352,18 @@ module Puma
293
352
  # object.
294
353
  #
295
354
  # The main body of the reactor loop is in `run_internal` and it
296
- # will sleep on `IO.select`. When a new connection is added to the
297
- # reactor it cannot be added directly to the `sockets` aray, because
298
- # the `IO.select` will not be watching for it yet.
355
+ # will sleep on `NIO::Selector#select`. When a new connection is added to the
356
+ # reactor it cannot be added directly to the `sockets` array, because
357
+ # the `NIO::Selector#select` will not be watching for it yet.
299
358
  #
300
- # Instead what needs to happen is that `IO.select` needs to be woken up,
359
+ # Instead what needs to happen is that `NIO::Selector#select` needs to be woken up,
301
360
  # the contents of `@input` added to the `sockets` array, and then
302
- # another call to `IO.select` needs to happen. Since the `Puma::Client`
361
+ # another call to `NIO::Selector#select` needs to happen. Since the `Puma::Client`
303
362
  # object can be read immediately, it does not block, but instead returns
304
363
  # right away.
305
364
  #
306
365
  # This behavior is accomplished by writing to `@trigger` which wakes up
307
- # the `IO.select` and then there is logic to detect the value of `*`,
366
+ # the `NIO::Selector#select` and then there is logic to detect the value of `*`,
308
367
  # pull the contents from `@input` and add them to the sockets array.
309
368
  #
310
369
  # If the object passed in has a timeout value in `timeout_at` then
@@ -315,13 +374,6 @@ module Puma
315
374
  @mutex.synchronize do
316
375
  @input << c
317
376
  @trigger << "*"
318
-
319
- if c.timeout_at
320
- @timeouts << c
321
- @timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
322
-
323
- calculate_sleep
324
- end
325
377
  end
326
378
  end
327
379