1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

Switch IO reactor to nio4r

This moves away from IO.select to using nio4r to allow the reactor to
scale beyond 1024 active clients. This happens when folks are using
websockets usually.
This commit is contained in:
Evan Phoenix 2019-02-19 16:38:21 -08:00
parent cee9a8e553
commit e83a4954e4
3 changed files with 49 additions and 20 deletions

View file

@ -5,6 +5,7 @@ gemspec
gem "rdoc"
gem "rake-compiler"
gem "nio4r", "~> 2.0"
gem "rack", "< 3.0"
gem "minitest", "~> 5.11"
gem "minitest-retry"

View file

@ -3,6 +3,8 @@
require 'puma/util'
require 'puma/minissl'
require 'nio'
module Puma
# Internal Docs, Not a public interface.
#
@ -49,6 +51,8 @@ module Puma
@events = server.events
@app_pool = app_pool
@selector = NIO::Selector.new
@mutex = Mutex.new
# Read / Write pipes to wake up internal while loop
@ -57,7 +61,10 @@ module Puma
@sleep_for = DefaultSleepFor
@timeouts = []
@sockets = [@ready]
mon = @selector.register(@ready, :r)
mon.value = :wakeup
@sockets = [mon]
end
private
@ -122,36 +129,48 @@ module Puma
# This calculation happens in `calculate_sleep`.
def run_internal
sockets = @sockets
selector = @selector
while true
begin
ready = IO.select sockets, nil, nil, @sleep_for
ready = selector.select @sleep_for
rescue IOError => e
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
if sockets.any? { |socket| socket.closed? }
if sockets.any? { |socket| m.value.closed? }
STDERR.puts "Error in select: #{e.message} (#{e.class})"
STDERR.puts e.backtrace
sockets = sockets.reject { |socket| socket.closed? }
sockets = sockets.reject do |socket|
if m.value.closed?
selector.deregister(socket)
true
end
end
retry
else
raise
end
end
if ready and reads = ready[0]
reads.each do |c|
if c == @ready
if ready
ready.each do |m|
if m.value == :wakeup
@mutex.synchronize do
case @ready.read(1)
when "*"
sockets += @input
sockets += @input.map { |i|
mon = selector.register(i, :r)
mon.value = i
mon
}
@input.clear
when "c"
sockets.delete_if do |s|
if s == @ready
sockets.delete_if do |sm|
if sm.value == :wakeup
false
else
s.close
sm.value.close
selector.deregister sm
true
end
end
@ -160,6 +179,8 @@ module Puma
end
end
else
c = m.value
# We have to be sure to remove it from the timeout
# list or we'll accidentally close the socket when
# it's in use!
@ -172,7 +193,8 @@ module Puma
begin
if c.try_to_finish
@app_pool << c
sockets.delete c
selector.deregister m
sockets.delete m
end
# Don't report these to the lowlevel_error handler, otherwise
@ -182,7 +204,8 @@ module Puma
c.write_500
c.close
sockets.delete c
selector.deregister m
sockets.delete m
# SSL handshake failure
rescue MiniSSL::SSLError => e
@ -193,7 +216,8 @@ module Puma
cert = ssl_socket.peercert
c.close
sockets.delete c
selector.deregister m
sockets.delete m
@events.ssl_error @server, addr, cert, e
@ -204,7 +228,8 @@ module Puma
c.write_400
c.close
sockets.delete c
selector.deregister m
sockets.delete m
@events.parse_error @server, c.env, e
rescue StandardError => e
@ -213,7 +238,8 @@ module Puma
c.write_500
c.close
sockets.delete c
selector.deregister m
sockets.delete m
end
end
end
@ -224,10 +250,13 @@ module Puma
now = Time.now
while @timeouts.first.timeout_at < now
c = @timeouts.shift
m = @timeouts.shift
c = m.value
c.write_408 if c.in_data_phase
c.close
sockets.delete c
selector.deregister m
sockets.delete m
break if @timeouts.empty?
end

View file

@ -1,7 +1,5 @@
# -*- encoding: utf-8 -*-
# This is only used when puma is a git dep from Bundler, keep in sync with Rakefile
version = File.read(File.expand_path("../lib/puma/const.rb", __FILE__))[/VERSION = "(\d+\.\d+\.\d+)"/, 1] || raise
Gem::Specification.new do |s|
@ -13,6 +11,7 @@ Gem::Specification.new do |s|
s.email = ["evan@phx.io"]
s.executables = ["puma", "pumactl"]
s.extensions = ["ext/puma_http11/extconf.rb"]
s.add_runtime_dependency "nio4r", "~> 2.0"
s.metadata["msys2_mingw_dependencies"] = "openssl"
s.files = `git ls-files -- bin docs ext lib tools`.split("\n") +
%w[History.md LICENSE README.md]