socketry/async-http

Async HTTP Server does not respond on Windows if cool.io gem is loaded

Closed this issue · 2 comments

I'm trying to update the async gem from v1.x to v2.x, which async-http gem depends on.
fluent/fluentd#3842

Then, I found a problem that Async HTTP Server stops responding If I use the async gem and cool.io gem under Windows.
It works well under Linux and macOS even if I use them.

Curiously, if I removed https://github.com/socketry/cool.io/blame/2735948698687b087f31c0e0056078dbf6d73a7f/ext/iobuffer/iobuffer.c#L104 line from cool.io gem, then Async HTTP server works.

Looks for me that async will change the behavior at https://github.com/socketry/async/blob/10fa816bb229d4df3433d337ded3421b43d161fe/lib/async/scheduler.rb#L253-L266 if cool.io is loaded.

Environment

Reproduce code

require 'bundler/inline'
gemfile do
  source 'https://rubygems.org'
  gem 'async-http'
  gem 'cool.io' # A problem is occurred if cool.io gem is loaded
end

require 'net/http'
require 'async/http/protocol'
require 'timeout'

module HttpServer
  class Router
    def initialize
      @router = { get: {} }
    end

    def mount(method, path, app)
      @router[method][path] = app
    end

    def route!(method, path, request)
      @router.fetch(method).fetch(path).call(request)
    end
  end

  class Server
    class App
      def initialize(router)
        @router = router
      end

      def call(request)
        method = request.method
        resp = get(request)

        Protocol::HTTP::Response[*resp]
      rescue => e
        Protocol::HTTP::Response[500, { 'Content-Type' => 'text/plain' }, 'Internal Server Error']
      end

      def get(request)
        @router.route!(:get, request.path, request)
      end
    end

    def initialize(addr:, port:, tls_context: nil)
      @uri = URI("http://#{addr}:#{port}").to_s
      @router = Router.new
      @server_task = nil

      @server = Async::HTTP::Server.new(App.new(@router), Async::HTTP::Endpoint.parse(@uri))
    end

    def start
      Async do |task|
        @server_task = task.async do
          @server.run
        end
      end
    end

    def stop
      @server_task&.stop
    end

    def get(path, app = nil, &block)
      @router.mount(:get, path, app || block)
    end
  end
end


def http_server_start(addr:, port:)
  server = HttpServer::Server.new(addr: addr, port: port)
  server.get('/api/plugins.json') { |req| [200, { 'Content-Type' => 'text/html' }, "Hello"] }

  Thread.new do
    server.start
  end

  sleep 1 # Wait until the server starts up.
end

#################################################

puts "[http server start]"
http_server_start(addr: "127.0.0.1", port: "8080")

puts "-" * 80
puts "[client request]"

Timeout.timeout(10) {
  p Net::HTTP.get(URI.parse("http://127.0.0.1:8080/api/plugins.json"))
}

Result on Windows

C:\tmp> ruby .\async-http.rb
[http server start]
--------------------------------------------------------------------------------
[client request]
C:/Ruby33-x64/lib/ruby/3.3.0/timeout.rb:43:in `rescue in handle_timeout': execution expired (Timeout::Error)
        from C:/Ruby33-x64/lib/ruby/3.3.0/timeout.rb:40:in `handle_timeout'
        from C:/Ruby33-x64/lib/ruby/3.3.0/timeout.rb:195:in `timeout'
        from ./async-http.rb:93:in `<main>'
C:/Ruby33-x64/lib/ruby/3.3.0/net/protocol.rb:229:in `wait_readable': execution expired (Timeout::ExitException)
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/protocol.rb:229:in `rbuf_fill'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/protocol.rb:199:in `readuntil'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/protocol.rb:209:in `readline'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http/response.rb:158:in `read_status_line'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http/response.rb:147:in `read_new'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:2342:in `block in transport_request'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:2333:in `catch'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:2333:in `transport_request'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:2306:in `request'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:2177:in `request_get'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:824:in `block in get_response'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:1570:in `start'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:1029:in `start'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:822:in `get_response'
        from C:/Ruby33-x64/lib/ruby/3.3.0/net/http.rb:803:in `get'
        from ./async-http.rb:94:in `block in <main>'
        from C:/Ruby33-x64/lib/ruby/3.3.0/timeout.rb:186:in `block in timeout'
        from C:/Ruby33-x64/lib/ruby/3.3.0/timeout.rb:41:in `handle_timeout'
        from C:/Ruby33-x64/lib/ruby/3.3.0/timeout.rb:195:in `timeout'
        from ./async-http.rb:93:in `<main>'

It got a timeout error because Async HTTP Server does not return any response.

Can you have any idea to solve this issue?

It looks like cool.io is unlikely to be compatible with Ruby 3.1+ as it introduces a class IO::Buffer which conflicts with Ruby's IO::Buffer (introduced in 3.1).

Unfortunately, cool.io should be considered legacy/deprecated and removed/replaced.

Thank you for quick reply.
OK, I see...
We have to plan to replace cool.io or something....