【发布时间】:2012-11-08 23:26:52
【问题描述】:
我试图理解和重新创建一个简单的预分叉服务器,就像独角兽一样,服务器在启动时分叉 4 个进程,这些进程都在控制套接字上等待(接受)。
控制套接字@control_socket 绑定到 9799 并生成 4 个等待接受连接的工作人员。每个工人所做的工作如下
def spawn_child
fork do
$STDOUT.puts "Forking child #{Process.pid}"
loop do
@client = @control_socket.accept
loop do
request = gets
if request
respond(@inner_app.call(request))
else
$STDOUT.puts("No Request")
@client.close
end
end
end
end
end
我使用了一个非常简单的机架应用程序,它只返回一个状态码为 200 的字符串和一个 text/html 的 Content-Type。
我面临的问题是,当我使用gets 而不是read 或read_partial 或@ 读取传入请求(通过点击“http://localhost:9799”处的url)时,我的服务器正常工作987654328@。当我使用非阻塞读取时,它似乎永远不会抛出 EOFError,根据我的理解,这意味着它没有收到 EOF 状态。
这会导致读取loop 无法完成。这是完成这项工作的代码 sn-p。
# Reads a file using IO.read_nonblock
# Returns end of file when using get but doesn't seem to return
# while using read_nonblock or readpartial
# The fact that the method is named gets is just bad naming, please ignore
def gets
buffer = ""
i =0
loop do
puts "loop #{i}"
i += 1
begin
buffer << @client.read_nonblock(READ_CHUNK)
puts "buffer is #{buffer}"
rescue Errno::EAGAIN => e
puts "#{e.message}"
puts "#{e.backtrace}"
IO.select([@client])
retry
rescue EOFError
$STDOUT.puts "-" * 50
puts "request data is #{buffer}"
$STDOUT.puts "-" * 50
break
end
end
puts "returning buffer"
buffer
end
但是,如果我使用简单的gets 而不是read 或read_nonblock,或者如果将IO.select([@client]) 替换为break,则代码可以完美运行。
这是代码工作并返回响应的时间。我打算使用 read_nonblock 的原因是 unicorn 使用了一个等效的 kgio 库,它实现了非阻塞读取。
def gets
@client.gets
end
接下来粘贴整个代码。
module Server
class Prefork
# line break
CRLF = "\r\n"
# number of workers process to fork
CONCURRENCY = 4
# size of each non_blocking read
READ_CHUNK = 1024
$STDOUT = STDOUT
$STDOUT.sync
# creates a control socket which listens to port 9799
def initialize(port = 21)
@control_socket = TCPServer.new(9799)
puts "Starting server..."
trap(:INT) {
exit
}
end
# Reads a file using IO.read_nonblock
# Returns end of file when using get but doesn't seem to return
# while using read_nonblock or readpartial
def gets
buffer = ""
i =0
loop do
puts "loop #{i}"
i += 1
begin
buffer << @client.read_nonblock(READ_CHUNK)
puts "buffer is #{buffer}"
rescue Errno::EAGAIN => e
puts "#{e.message}"
puts "#{e.backtrace}"
IO.select([@client])
retry
rescue EOFError
$STDOUT.puts "-" * 50
puts "request data is #{buffer}"
$STDOUT.puts "-" * 50
break
end
end
puts "returning buffer"
buffer
end
# responds with the data and closes the connection
def respond(data)
puts "request 2 Data is #{data.inspect}"
status, headers, body = data
puts "message is #{body}"
buffer = "HTTP/1.1 #{status}\r\n" \
"Date: #{Time.now.utc}\r\n" \
"Status: #{status}\r\n" \
"Connection: close\r\n"
headers.each {|key, value| buffer << "#{key}: #{value}\r\n"}
@client.write(buffer << CRLF)
body.each {|chunk| @client.write(chunk)}
ensure
$STDOUT.puts "*" * 50
$STDOUT.puts "Closing..."
@client.respond_to?(:close) and @client.close
end
# The main method which triggers the creation of workers processes
# The workers processes all wait to accept the socket on the same
# control socket allowing the kernel to do the load balancing.
#
# Working with a dummy rack app which returns a simple text message
# hence the config.ru file read.
def run
# copied from unicorn-4.2.1
# refer unicorn.rb and lib/unicorn/http_server.rb
raw_data = File.read("config.ru")
app = "::Rack::Builder.new {\n#{raw_data}\n}.to_app"
@inner_app = eval(app, TOPLEVEL_BINDING)
child_pids = []
CONCURRENCY.times do
child_pids << spawn_child
end
trap(:INT) {
child_pids.each do |cpid|
begin
Process.kill(:INT, cpid)
rescue Errno::ESRCH
end
end
exit
}
loop do
pid = Process.wait
puts "Process quit unexpectedly #{pid}"
child_pids.delete(pid)
child_pids << spawn_child
end
end
# This is where the real work is done.
def spawn_child
fork do
$STDOUT.puts "Forking child #{Process.pid}"
loop do
@client = @control_socket.accept
loop do
request = gets
if request
respond(@inner_app.call(request))
else
$STDOUT.puts("No Request")
@client.close
end
end
end
end
end
end
end
p = Server::Prefork.new(9799)
p.run
有人可以向我解释为什么读取会因“read_partial”或“read_nonblock”或“read”而失败。我真的很感激这方面的一些帮助。
谢谢。
【问题讨论】:
-
您描述的行为与文档
EOFError、read_nonblock等所说的相反。get应该返回nil,read_nonblock应该提高EOFError。 -
如果你只启动一个工人会发生什么?在
spawn_child方法中分配一个实例变量@client对我来说很奇怪。每个工人不会覆盖该变量吗?或者,fork 是否建立了自己的上下文?
标签: ruby unix nonblocking unicorn preforking