Próbuję zrozumieć i odtworzyć prosty serwer prefabrykacji wzdłuż linii jednorożca, gdzie serwer na początku widnieje 4 procesy, które wszystkie czekają (aby zaakceptować) na kontrolnym gnieździe.Ruby readpartial i read_nonblock nie rzucają EOFError
Gniazdo sterujące @control_socket
łączy się z 9799 i spawnuje 4 pracowników oczekujących na połączenie. Praca odbywa się na każdego pracownika jest następujący
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
Użyłem bardzo prostą aplikację w szafie, które po prostu zwraca ciąg znaków z kodem statusu 200 i Content-Type of text/html.
Problemem jest to, że mogę zmierzyć mój serwer działa tak jak powinien, kiedy czytam przychodzące żądania (trafiając url w „http://localhost:9799”) za pomocą gets
zamiast coś read
lub read_partial
lub read_nonblock
. Kiedy używam nie blokujących odczytów, nigdy nie wydaje mi się, że EOFError, który zgodnie z moim rozumowaniem oznacza, że nie otrzymuje stanu EOF
.
To powoduje, że przeczytany loop
nie jest kompletny. Oto fragment kodu, który wykonuje tę pracę.
# 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
Jednak kod działa doskonale, jeśli mogę użyć prostego gets
zamiast read
lub read_nonblock
czy wymienić IO.select([@client])
z break
.
Oto, kiedy kod działa i zwraca odpowiedź. Powodem, dla którego zamierzam użyć read_nonblock jest jednorożec używa odpowiednika przy użyciu biblioteki kgio, która implementuje odczyt bez blokowania.
def gets
@client.gets
end
Cały kod zostanie wklejony dalej.
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
Może ktoś mi wytłumaczyć, dlaczego nie czyta z „read_partial” lub „read_nonblock” lub „przeczytane”. Byłbym wdzięczny za pomoc w tej sprawie.
Dzięki.
Zachowanie, które opisujesz, jest przeciwieństwem tego, co mówią dokumenty "EOFError", "read_nonblock" itp. 'get' powinno zwracać' nil', 'read_nonblock' powinno wywoływać' EOFError'. –
Co się dzieje, jeśli uruchamiasz tylko jednego pracownika? To dla mnie dziwne, że przypisujesz zmienną instancji '' '@ client'''' w metodzie' 'spawn_child'''''. Czy każdy pracownik nie zastąpi tej zmiennej? Lub czy widelec ustanawia swój własny kontekst? – GSP