Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"private method `raise' called for nil (NoMethodError)" error if stop Async HTTP server #176

Closed
Watson1978 opened this issue Aug 28, 2024 · 3 comments

Comments

@Watson1978
Copy link

I'm trying to launch an Async HTTP server like following:

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

Then, I called stop method on a task created with task.async to stop the http server, like:

  def stop
    @server_task&.stop
  end

and, I got a NoMethodError error.
Is there something wrong with my code?
Please see Reproduction code and Result sections in below.

Environment

  • Ruby 3.3.4
  • async 2.16.1
  • async-http 0.71.0

Reproduction code

require 'bundler/inline'
gemfile do
  source 'https://rubygems.org'
  gem 'async-http'
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.

  server
end

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

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

server.stop

Result

$ ruby async-http.rb
[http server start]
/home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/task.rb:288:in `stop': private method `raise' called for nil (NoMethodError)

                                                Fiber.scheduler.raise(@fiber, Stop)
                                                               ^^^^^^
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/node.rb:291:in `block in stop_children'
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/list.rb:290:in `each'
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/list.rb:303:in `each'
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/list.rb:178:in `each'
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/node.rb:290:in `stop_children'
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/task.rb:401:in `stopped!'
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/task.rb:414:in `stop!'
        from /home/watson/.rbenv/versions/3.3.4/lib/ruby/gems/3.3.0/gems/async-2.16.1/lib/async/task.rb:296:in `stop'
        from t.rb:63:in `stop'
        from t.rb:91:in `<main>'
@ioquatix
Copy link
Member

I will check it.

@ioquatix
Copy link
Member

If you want to invoke stop across threads, you wil need to use an appropriate mechanism, e.g.

    def start
      @stop = Thread::Queue.new
      
      Async do |task|
        @server_task = task.async do
          @server.run
        end
        
        @stop.pop
        @server_task.stop
      end
    end

    def stop
      @stop.push(true)
    end

I didn't test this, but something like this will probably work.

@Watson1978
Copy link
Author

Thanks!!
I was able to stop the server that way without error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants