socketry / falcon

A high-performance web server for Ruby, supporting HTTP/1, HTTP/2 and TLS.
https://socketry.github.io/falcon/
MIT License
2.62k stars 79 forks source link

crashes on launch with ruby 3.2 #205

Closed timuckun closed 8 months ago

timuckun commented 1 year ago

Simple rack app with two endpoints.

def call(env)
    case env["PATH_INFO"]
    when "/json"
      # Test type 1: JSON serialization
      respond JSON_TYPE,
              Oj.dump({ message: "Hello, World!" }, { mode: :strict })
    when "/plaintext"
      # Test type 6: Plaintext
      respond PLAINTEXT_TYPE, "Hello, World!"
    end
  end

config.ru

require_relative "hello_world.rb"

run HelloWorld.new

falcon.rb

#!/usr/bin/env -S falcon host
# frozen_string_literal: true

load :rack, :supervisor

hostname = File.basename(__dir__)
rack hostname do
  endpoint Async::HTTP::Endpoint.parse("http://0.0.0.0:8080").with(
             protocol: Async::HTTP::Protocol::HTTP11
           )
end

supervisor

runing the benchmark

Concurrency: 512 for json wrk -H 'Host: tfb-server' -H 'Accept: application/json,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,/;q=0.7' -H 'Connection: keep-alive' --latency -d 15 -c 512 --timeout 8 -t 3 "http://tfb-server:8080/json"

I get a lot of these errors

{"time":"2023-04-29T10:08:20+00:00","severity":"warn","class":"Async::Task","oid":6140,"pid":11,"subject":"#<Async::Task:0x00000000000017fc Reading HTTP/1.1 requests for Async::HTTP::Protocol::HTTP1::Server. (failed)>","message":["Task may have ended with unhandled exception.","Connection reset by peer"],"error":{"kind":"Errno::ECONNRESET","message":"Connection reset by peer","stack":"/usr/local/lib/ruby/3.2.0/socket.rb:456:in __read_nonblock'\n/usr/local/lib/ruby/3.2.0/socket.rb:456:inread_nonblock'\n/usr/local/bundle/gems/async-io-1.34.3/lib/async/io/generic.rb:216:in async_send'\n/usr/local/bundle/gems/async-io-1.34.3/lib/async/io/generic.rb:69:inblock in wrap_blocking_method'\n/usr/local/bundle/gems/async-io-1.34.3/lib/async/io/stream.rb:263:in fill_read_buffer'\n/usr/local/bundle/gems/async-io-1.34.3/lib/async/io/stream.rb:133:inread_until'\n/usr/local/bundle/gems/async-http-0.60.1/lib/async/http/protocol/http1/connection.rb:51:in read_line?'\n/usr/local/bundle/gems/protocol-http1-0.15.0/lib/protocol/http1/connection.rb:160:inread_request'\n/usr/local/bundle/gems/async-http-0.60.1/lib/async/http/protocol/http1/request.rb:31:in read'\n/usr/local/bundle/gems/async-http-0.60.1/lib/async/http/protocol/http1/server.rb:40:innext_request'\n/usr/local/bundle/gems/async-http-0.60.1/lib/async/http/protocol/http1/server.rb:61:in each'\n/usr/local/bundle/gems/async-http-0.60.1/lib/async/http/server.rb:56:inaccept'\n/usr/local/bundle/gems/async-io-1.34.3/lib/async/io/server.rb:32:in block in accept_each'\n/usr/local/bundle/gems/async-io-1.34.3/lib/async/io/socket.rb:73:inblock in accept'\n/usr/local/bundle/gems/async-2.5.0/lib/async/task.rb:158:in block in run'\n/usr/local/bundle/gems/async-2.5.0/lib/async/task.rb:310:inblock in schedule'\n"}}

ioquatix commented 1 year ago

What OS? What is your file descriptor limit?

timuckun commented 1 year ago

The OS is mac but the test is running in docker linux. Both the client and the server are running in docker containers.

dockerfile

FROM ruby:3.2

ENV BUNDLE_FORCE_RUBY_PLATFORM=true ENV RUBY_YJIT_ENABLE=1

WORKDIR /rack

COPY Gemfile Gemfile.lock ./ RUN bundle install --jobs=8

COPY . .

EXPOSE 8080

CMD bundle exec falcon host

ioquatix commented 1 year ago

Okay let me investigate.

timuckun commented 1 year ago

Thanks.

ioquatix commented 1 year ago

Does it crash with YJIT not enabled?

timuckun commented 1 year ago

Yes it does.

ioquatix commented 1 year ago

In

  def call(env)
    case env["PATH_INFO"]
    when "/json"
      # Test type 1: JSON serialization
      respond JSON_TYPE,
              Oj.dump({ message: "Hello, World!" }, { mode: :strict })
    when "/plaintext"
      # Test type 6: Plaintext
      respond PLAINTEXT_TYPE, "Hello, World!"
    end
  end

how is respond implemented?

From my own testing in the past, wrk does not gracefully close the connection. So it's possible to see one "Connection reset by peer" for each 512 connections in your test.

Better to use my fork: https://github.com/ioquatix/wrk

ioquatix commented 8 months ago

When using my fork of wrk, which correctly closes connections, I do not observe the errors. The logged errors are a symptom of wrk closing connections mid-response.