Skip to content

Commit

Permalink
start using kgio, the kinder, gentler I/O library
Browse files Browse the repository at this point in the history
This should hopefully make the non-blocking accept()
situation more tolerable under Ruby 1.9.2.
  • Loading branch information
Eric Wong committed Oct 5, 2010
1 parent 9ef6b6f commit 2994636
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 21 deletions.
28 changes: 13 additions & 15 deletions lib/unicorn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'etc'
require 'stringio'
require 'rack'
require 'kgio'
require 'unicorn/socket_helper'
require 'unicorn/const'
require 'unicorn/http_request'
Expand Down Expand Up @@ -194,7 +195,7 @@ def start
BasicSocket.do_not_reverse_lookup = true

# inherit sockets from parents, they need to be plain Socket objects
# before they become UNIXServer or TCPServer
# before they become Kgio::UNIXServer or Kgio::TCPServer
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
io = Socket.for_fd(fd.to_i)
set_server_sockopt(io, listener_opts[sock_name(io)])
Expand All @@ -207,9 +208,10 @@ def start
LISTENERS.replace(inherited)

# we start out with generic Socket objects that get cast to either
# TCPServer or UNIXServer objects; but since the Socket objects
# share the same OS-level file descriptor as the higher-level *Server
# objects; we need to prevent Socket objects from being garbage-collected
# Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
# objects share the same OS-level file descriptor as the higher-level
# *Server objects; we need to prevent Socket objects from being
# garbage-collected
config_listeners -= listener_names
if config_listeners.empty? && LISTENERS.empty?
config_listeners << Unicorn::Const::DEFAULT_LISTEN
Expand Down Expand Up @@ -320,7 +322,7 @@ def listen(address, opt = {}.merge(listener_opts[address] || {}))
tries = opt[:tries] || 5
begin
io = bind_listen(address, opt)
unless TCPServer === io || UNIXServer === io
unless Kgio::TCPServer === io || Kgio::UNIXServer === io
IO_PURGATORY << io
io = server_cast(io)
end
Expand Down Expand Up @@ -449,13 +451,11 @@ def trap_deferred(signal)
# Wake up every second anyways to run murder_lazy_workers
def master_sleep(sec)
IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
SELF_PIPE[0].read_nonblock(Const::CHUNK_SIZE)
rescue Errno::EAGAIN, Errno::EINTR
SELF_PIPE[0].kgio_tryread(Const::CHUNK_SIZE)
end

def awaken_master
SELF_PIPE[1].write_nonblock('.') # wakeup master process from select
rescue Errno::EAGAIN, Errno::EINTR
SELF_PIPE[1].kgio_trywrite('.') # wakeup master process from select
end

# reaps all unreaped workers
Expand Down Expand Up @@ -581,7 +581,7 @@ def handle_error(client, e)
logger.error e.backtrace.join("\n")
Const::ERROR_500_RESPONSE
end
client.write_nonblock(msg)
client.kgio_trywrite(msg)
client.close
rescue
nil
Expand All @@ -590,7 +590,6 @@ def handle_error(client, e)
# once a client is accepted, it is processed in its entirety here
# in 3 easy steps: read request, call app, write app response
def process_client(client)
client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
r = app.call(env = REQUEST.read(client))

if 100 == r[0].to_i
Expand Down Expand Up @@ -665,11 +664,10 @@ def worker_loop(worker)
alive.chmod(m = 0 == m ? 1 : 0)

ready.each do |sock|
begin
process_client(sock.accept_nonblock)
if client = sock.kgio_tryaccept
process_client(client)
nr += 1
alive.chmod(m = 0 == m ? 1 : 0)
rescue Errno::EAGAIN, Errno::ECONNABORTED
end
break if nr < 0
end
Expand Down Expand Up @@ -773,7 +771,7 @@ def redirect_io(io, path)

def init_self_pipe!
SELF_PIPE.each { |io| io.close rescue nil }
SELF_PIPE.replace(IO.pipe)
SELF_PIPE.replace(Kgio::Pipe.new)
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
end

Expand Down
3 changes: 1 addition & 2 deletions lib/unicorn/http_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class Unicorn::HttpRequest
}

NULL_IO = StringIO.new("")
LOCALHOST = '127.0.0.1'

# :stopdoc:
# A frozen format for this is about 15% faster
Expand Down Expand Up @@ -62,7 +61,7 @@ def read(socket)
# identify the client for the immediate request to the server;
# that client may be a proxy, gateway, or other intermediary
# acting on behalf of the actual source client."
@env[REMOTE_ADDR] = TCPSocket === socket ? socket.peeraddr[-1] : LOCALHOST
@env[REMOTE_ADDR] = socket.kgio_addr

# short circuit the common case with small GET requests first
if @parser.headers(@env, socket.readpartial(16384, @buf)).nil?
Expand Down
8 changes: 4 additions & 4 deletions lib/unicorn/socket_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,12 @@ def bind_listen(address = '0.0.0.0:8080', opt = {})
end
old_umask = File.umask(opt[:umask] || 0)
begin
UNIXServer.new(address)
Kgio::UNIXServer.new(address)
ensure
File.umask(old_umask)
end
elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
TCPServer.new($1, $2.to_i)
Kgio::TCPServer.new($1, $2.to_i)
else
raise ArgumentError, "Don't know how to bind: #{address}"
end
Expand Down Expand Up @@ -166,9 +166,9 @@ def sock_name(sock)
def server_cast(sock)
begin
Socket.unpack_sockaddr_in(sock.getsockname)
TCPServer.for_fd(sock.fileno)
Kgio::TCPServer.for_fd(sock.fileno)
rescue ArgumentError
UNIXServer.for_fd(sock.fileno)
Kgio::UNIXServer.for_fd(sock.fileno)
end
end

Expand Down
1 change: 1 addition & 0 deletions script/isolate_for_tests
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ opts = {
pid = fork do
Isolate.now!(opts) do
gem 'sqlite3-ruby', '1.2.5'
gem 'kgio', '1.1.0'
gem 'rack', '1.1.0'
end
end
Expand Down
4 changes: 4 additions & 0 deletions test/unit/test_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class RequestTest < Test::Unit::TestCase
class MockRequest < StringIO
alias_method :readpartial, :sysread
alias_method :read_nonblock, :sysread
def kgio_addr
'127.0.0.1'
end
end

def setup
Expand Down Expand Up @@ -159,6 +162,7 @@ def test_rack_lint_big_put
buf = (' ' * bs).freeze
length = bs * count
client = Tempfile.new('big_put')
def client.kgio_addr; '127.0.0.1'; end
client.syswrite(
"PUT / HTTP/1.1\r\n" \
"Host: foo\r\n" \
Expand Down
1 change: 1 addition & 0 deletions unicorn.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Gem::Specification.new do |s|
# commented out. Nevertheless, upgrading to Rails 2.3.4 or later is
# *strongly* recommended for security reasons.
s.add_dependency(%q<rack>)
s.add_dependency(%q<kgio>, '~> 1.1.0')

s.add_development_dependency('isolate', '~> 2.0.2')

Expand Down

0 comments on commit 2994636

Please sign in to comment.