Ich versuche zu verstehen und neu erstellen einen einfachen Preforking-Server nach dem Vorbild der Einhorn, wo der Server auf Startgabeln 4 verarbeitet, die alle auf dem steuernden Socket warten (zu akzeptieren).Ruby readpartial und read_nonblock nicht werfen EOFError
Der steuernde Socket @control_socket
bindet an 9799 und erzeugt 4 Arbeiter, die darauf warten, eine Verbindung zu akzeptieren. Die Arbeit an jedem Arbeiter getan ist wie folgt
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
ich einen sehr einfachen Rack-App verwendet habe, die einfach einen String mit dem Statuscode 200 zurück und einem Content-Type text/html.
Das Problem, das ich Gesicht ist, dass mein Server funktioniert wie es soll, wenn ich eingehende Anfragen lesen (durch die URL auf „http://localhost:9799“ Schlagen) ein gets
anstatt etwas wie read
oder read_partial
oder read_nonblock
verwenden. Wenn ich nicht blockierende Lesevorgänge benutze, scheint es nie den EOFError zu werfen, der meiner Meinung nach bedeutet, dass er den EOF
Zustand nicht empfängt.
Dies führt dazu, dass der Lesevorgang loop
nicht abgeschlossen wird. Hier ist das Code-Snippet, das diese Arbeit macht.
# 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
jedoch der Code funktioniert perfekt, wenn die ich eine einfache gets
statt read
verwenden oder read_nonblock
oder wenn die IO.select([@client])
mit einem break
ersetzen.
Hier ist, wenn der Code funktioniert und die Antwort zurückgibt. Der Grund, warum ich read_nonblock verwenden möchte, ist Unicorn, das ein Äquivalent verwendet, das die kgio-Bibliothek verwendet, die einen nicht blockierenden Lesevorgang implementiert.
def gets
@client.gets
end
Der gesamte Code wird als nächstes eingefügt.
require 'socket' require 'builder' require 'rack' require 'pry' 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
Könnte jemand mir erklären, warum die mit ‚read_partial‘ liest versagen oder ‚read_nonblock‘ oder ‚lesen‘. Ich würde wirklich etwas Hilfe bei diesem Thema schätzen.
Danke.
Das Verhalten, das Sie beschreiben, ist das Gegenteil von dem, was die Dokumente 'EOFError',' read_nonblock' usw. sagen. 'get' sollte' nil' zurückgeben, 'read_nonblock' sollte' EOFError' auslösen. –
Was passiert, wenn Sie nur einen einzelnen Mitarbeiter starten? Es ist seltsam für mich, dass Sie eine Instanzvariable '' '' 'client'''' in der' '' spawn_child'''' Methode zuweisen. Würde nicht jeder Arbeiter diese Variable überschreiben? Oder stellt fork seinen eigenen Kontext her? – GSP