How do I shut a server down from a handler when only its owner may close it?

← Error Handling and Exceptions · Ref: Q1700

Call the server's stop(). A server that serves in a loop - TCP's accept(handler) and acceptUntilComplete(handler, readyHandler), HTTP's serve() and serveUntilComplete(readyHandler) - runs until stop() is called, from a connection handler, a signal handler or a control connection, on any thread. stop() ends serving WITHOUT closing: accept() or serve() returns true, and the server stays set, still open and still owned, until the try that made it ends and closes it.

A handler cannot close the server instead. The handler captured the server, and a capture belongs to the scope that made it - the try, whose generated close closes it. There is no 'close' statement (E01094), and 'server.close()' is E81060: closing it in the handler would close it twice (once in the handler, once when the try ends) and would close it while accept() was still using it. Only the owner closes; everyone else stops.

The program below makes the server a try-with-resources resource. Its handler reads one request and, on 'shutdown', calls server.stop(). A client connects before accept() is called - a TCP server listens from construction, so the connection waits in the backlog - and accept() serves it and then returns. The program prints that the server is still set after accept() returns, and the end of the try closes it.

For HTTP the shape is the same: make the HTTP a try resource, call httpServer.serve(), and call httpServer.stop() from a signal handler (Signals().register) or an admin operation; serve() returns once in-flight requests have drained, and the try closes the server. A stopped server cannot serve again - make a new one.

acceptUntilComplete and serveUntilComplete stop the server for you when their ready handler (a TCPReadyHandler / HTTPReadyHandler, or a pure TCPReadyObserver / HTTPReadyObserver) ends - normally, by Exception or by Panic - and they too only stop it: the try still closes it. See Q1760.

Example

defines module qa.errorhandling.stopaserver

  defines function

    sendShutdown()
      -> port as Integer
      loopback <- Networking().loopbackV4()
      try
        -> connection <- TCP(NetworkProperties(loopback, port)).connect()
        if connection?
          connection.output().println("shutdown")

  defines program

    StopFromAHandler()
      stdout <- Stdout()
      port <- 28207

      try
        -> server <- TCP(NetworkProperties(port: port, backlog: 2, maxConcurrent: 2, localOnly: true))
        stopOnRequest <- (server, stdout) is TCPHandler as function
          request <- input.next()
          stdout.println(`handler received ${request}`)
          if request == "shutdown"
            server.stop()

        sendShutdown(port)
        served <- server.accept(stopOnRequest)
        stdout.println(`accept returned ${served}, server still set ${server?}`)

      stdout.println("the try has closed the server")

Common mistakes

E81060 — The handler captured the server, and a capture belongs to the try that made it, which closes it. Closing it in the handler would close it twice, and while accept() was still serving. Call server.stop() - it ends accept() without closing. See ek9 -h E81060 for details.

Incorrect:

          if request == "shutdown"
            server.close()

Correct:

          if request == "shutdown"
            server.stop()
Other ways to ask this
  • How do I stop a TCP server from inside its connection handler?
  • How do I end HTTP serve() from a signal handler or admin endpoint?
  • What is the difference between stop() and close on a server?
  • Why can a handler not close the server it captured?
  • How does accept() return so the program can exit?

Coming from another language?

Java: ServerSocket.close() from another thread both ends accept() and releases the socket, so any code holding the server can shut it down - and the try-with-resources that owns it closes it again. Jetty/Undertow separate stop() from destroy(). Go: http.Server.Shutdown(ctx) stops serving and Close() releases. Rust (tokio): a shutdown signal ends the accept loop; the listener is dropped by its owner. EK9: stop() ends serving and only the owner closes - 'server.close()' is E81060.

Keywords: owner, HTTP, accept, stop, TCP, server, E81060, serve, close, shutdown, handler