Gracefully shutdown HTTP Server


#1

I have tried to put server.close after server.listen which don’t close and remain listening for request, how do I really shutdown the server gracefully?


#2

server.listen is a blocking call and waits until listen finishes - which it does only when crashing.
spawn server.listen is what you might need to be able to call server.close at some point.


#3

I’m not sure how it handles if I have run twice on the code below on the terminal/console to create 2 processes in macOS.

Tested on wrk benchmark on 127.0.0.1:8081, the first process will be stress load until kill -1 1234 the 2nd process will take over incoming request but wrk appear to show error requests which the 1st process wasn’t shutdown gracefully, I think my code need more robust solutions or any better way to manage it?

require "http"
  
server1 = HTTP::Server.new do |ctx|
  ctx.response.status_code = 200
  ctx.response.headers["Content-Type"] = "text/plain"
  ctx.response.puts "Hello World! #1"
end
server1.bind_tcp host: "127.0.0.1", port: 8081, reuse_port: true

Signal::HUP.trap do
  puts "Will stop in moment! ({active_requests} active requests)"
  sleep 1.seconds
  server1.close
end

spawn { server1.listen }
sleep

#4

I would say reuse_port is not the best way to go about here. My first pick would be a load balancer like haproxy or nginx in front of both servers.

Having said that, the root of the issue as I see it is the word “gracefully”. server1.close tries to do that:

  # Gracefully terminates the server. It will process currently accepted
  # requests, but it won't accept new connections.
  def close
    raise "Can't close server, it's already closed" if closed?

    @closed = true
    @processor.close

    @sockets.each do |socket|
      socket.close
    rescue
      # ignore exception on close
    end

    @listening = false
    @sockets.clear
end

It doesn’t accept new connections, but it doesn’t close the sockets immediately either. You have to decide do you want the existing clients to be served before the new instance takes the port over, or do you want to close the sockets immediately, breaking all current requests. Both options are not entirely graceful.