How to stop http.ListenAndServe()
Asked Answered
go
A

10

141

I am using the Mux library from Gorilla Web Toolkit along with the bundled Go http server.

The problem is that in my application the HTTP server is only one component and it is required to stop and start at my discretion.

When I call http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router) it blocks and I cannot seem to stop the server from running.

I am aware this has been a problem in the past, is that still the case? Are there any new solutions?

Arleanarlee answered 4/9, 2016 at 18:12 Comment(0)
P
130

Regarding graceful shutdown (introduced in Go 1.8), a bit more concrete example:

package main

import (
    "context"
    "io"
    "log"
    "net/http"
    "sync"
    "time"
)

func startHttpServer(wg *sync.WaitGroup) *http.Server {
    srv := &http.Server{Addr: ":8080"}

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello world\n")
    })

    go func() {
        defer wg.Done() // let main know we are done cleaning up

        // always returns error. ErrServerClosed on graceful close
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            // unexpected error. port in use?
            log.Fatalf("ListenAndServe(): %v", err)
        }
    }()

    // returning reference so caller can call Shutdown()
    return srv
}

func main() {
    log.Printf("main: starting HTTP server")

    httpServerExitDone := &sync.WaitGroup{}

    httpServerExitDone.Add(1)
    srv := startHttpServer(httpServerExitDone)

    log.Printf("main: serving for 10 seconds")

    time.Sleep(10 * time.Second)

    log.Printf("main: stopping HTTP server")

    // now close the server gracefully ("shutdown")
    // timeout could be given with a proper context
    // (in real world you shouldn't use TODO()).
    if err := srv.Shutdown(context.TODO()); err != nil {
        panic(err) // failure/timeout shutting down the server gracefully
    }

    // wait for goroutine started in startHttpServer() to stop
    httpServerExitDone.Wait()

    log.Printf("main: done. exiting")
}
Phosphate answered 1/3, 2017 at 13:42 Comment(9)
Yes, the feature is Shutdown(), whose concrete usage I'm demonstrating here. Thanks, I should've been more clear, I changed the heading to this now: "Regarding graceful shutdown (introduced in Go 1.8), a bit more concrete example:"Phosphate
When I pass nil to srv.Shutdown I get panic: runtime error: invalid memory address or nil pointer dereference. Passing context.Todo() instead works.Huppert
@Huppert that's weird, I just tried this on latest Golang version (1.10), and it ran fine. context.Background() or context.TODO() of course works and if it works for you, good. :)Phosphate
"NOTE: there is a chance that next line won't have time to run..." How can we make sure the code in the error handling gets ran?Cooperation
@newplayer65 there's multiple ways to do that. One way could be to create sync.WaitGroup in main(), call Add(1) on it and pass a pointer to it to startHttpServer() and call defer waitGroup.Done() at start of the goroutine that has a call to the ListenAndServe(). then just call waitGroup.Wait() at end of main() to wait for the goroutine to have finished its job.Phosphate
I've already figured it out, but thanks! I made a goroutine run the ListenAndServe() and have it send it's error over a chan error to the main thread. So, I block the main thread, and receive the error there to do whatever I want with it on the main thread when the server shuts down :) I actually started a new question for this: #53908470 and have my solution posted in my edit.Cooperation
@newplayer65 I looked at your code. Using a channel is a good, probably better option than my suggestion. My code was mainly to demonstrate Shutdown() - not showcase production quality code :) P.s. your project's "server gopher" logo is adorbs! :DPhosphate
@Phosphate thanks a lot! Always feels nice to get a positive code/repo review <3Cooperation
Is httpServerExitDone really necessary? Other examples suggest that at exit of Shutdown, it is guaranteed that ListenAndServe has exited.Lewie
S
127

As mentioned in yo.ian.g's answer. Go 1.8 has included this functionality in the standard lib.

Minimal example for for Go 1.8+:

    server := &http.Server{Addr: ":8080", Handler: handler}

    go func() {
        if err := server.ListenAndServe(); err != nil {
            // handle err
        }
    }()

    // Setting up signal capturing
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, os.Interrupt)

    // Waiting for SIGINT (kill -2)
    <-stop

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := server.Shutdown(ctx); err != nil {
        // handle err
    }

    // Wait for ListenAndServe goroutine to close.

You can kill the server gracefully using kill -2 <pid>


Original Answer - Pre Go 1.8 :

Building on Uvelichitel's answer.

You can create your own version of ListenAndServe which returns an io.Closer and does not block.

func ListenAndServeWithClose(addr string, handler http.Handler) (io.Closer,error) {

    var (
        listener  net.Listener
        srvCloser io.Closer
        err       error
    )

    srv := &http.Server{Addr: addr, Handler: handler}

    if addr == "" {
        addr = ":http"
    }

    listener, err = net.Listen("tcp", addr)
    if err != nil {
        return nil, err
    }

    go func() {
        err := srv.Serve(tcpKeepAliveListener{listener.(*net.TCPListener)})
        if err != nil {
            log.Println("HTTP Server Error - ", err)
        }
    }()
    
    srvCloser = listener
    return srvCloser, nil
}

Full code available here.

The HTTP Server will close with the error accept tcp [::]:8080: use of closed network connection

Stenson answered 14/10, 2016 at 10:58 Comment(1)
I created a package which does the boilerplate for you github.com/pseidemann/finishArtemisia
S
28

Go 1.8 will include graceful and forceful shutdown, available via Server::Shutdown(context.Context) and Server::Close() respectively.

go func() {
    httpError := srv.ListenAndServe(address, handler)
    if httpError != nil {
        log.Println("While serving HTTP: ", httpError)
    }
}()

srv.Shutdown(context)

The relevant commit can be found here

Sphenoid answered 2/1, 2017 at 21:33 Comment(1)
sorry to be picky, and I know you're code was purely example usage, but as a general rule: go func() { X() }() followed by Y() makes the false assumption to the reader that X() will execute before Y(). Waitgroups etc. ensure timing bugs like this don't bite you when least expected!Binnie
C
23

You can construct net.Listener

l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port()))
if err != nil {
    log.Fatal(err)
}

which you can Close()

go func(){
    //...
    l.Close()
}()

and http.Serve() on it

http.Serve(l, service.router)
Cheer answered 4/9, 2016 at 19:9 Comment(3)
thanks but that doesn't answer my question. I am asking about http.ListenAndServe for specific reasons. That is how I use GWT MUX library, i'm not sure how to use net.listen for that..Arleanarlee
You use http.Serve() instead of http.ListenAndServe() exactly same way with same syntax just with own Listener. http.Serve(net.Listener, gorilla.mux.Router)Cheer
Bit late, but we've been using the manners package for this use case. It's a drop-in replacement for the standard http package that allows graceful shutdown (ie. finishes all active requests while declining new ones, then exits).Geez
B
22

Since none of the previous answers say why you can't do it if you use http.ListenAndServe(), I went into the v1.8 http source code and here is what it says:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

As you can see the http.ListenAndServe function does not return the server variable. This means you cannot get to 'server' to use the Shutdown command. Therefore, you need to create your own 'server' instance instead of using this function for the graceful shutdown to be implemented.

Bowker answered 10/5, 2017 at 12:19 Comment(0)
M
4

You can close the server by closing its context.

type ServeReqs func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error

var ServeReqsImpl = func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error {
    http.Handle(pingRoute, decorateHttpRes(pingHandlerImpl(deps.pingRouteResponseMessage), addJsonHeader()))

    server := &http.Server{Addr: fmt.Sprintf(":%d", cfg.port), Handler: nil}

    go func() {
        <-ctx.Done()
        fmt.Println("Shutting down the HTTP server...")
        server.Shutdown(ctx)
    }()

    err := server.ListenAndServeTLS(
        cfg.certificatePemFilePath,
        cfg.certificatePemPrivKeyFilePath,
    )

    // Shutting down the server is not something bad ffs Go...
    if err == http.ErrServerClosed {
        return nil
    }

    return err
}

And whenever you are ready to close it, call:

ctx, closeServer := context.WithCancel(context.Background())
err := ServeReqs(ctx, etc)
closeServer()
Mongoose answered 22/10, 2018 at 19:12 Comment(3)
"Shutting down the server is not something bad ffs Go..." :)Surfboat
One thing worth noting is that, for a graceful shutdown, before exiting you need to wait for Shutdown to return which doesn't seem to be happening here.Buckley
Your use of ctx to server.Shutdown is incorrect. The context is already cancelled so it will not be shutdown cleanly. You might have well called server.Close for an unclean shutdown. (For a clean shutdown this code would need to be extensively re-worked.Paulie
T
2

It is possible to solve this using a context.Context using a net.ListenConfig. In my case, I didn't want to use a sync.WaitGroup or http.Server's Shutdown() call, and instead rely on a context.Context (which was closed with a signal).

import (
  "context"
  "http"
  "net"
  "net/http/pprof"
)

func myListen(ctx context.Context, cancel context.CancelFunc) error {
  lc := net.ListenConfig{}
  ln, err := lc.Listen(ctx, "tcp4", "127.0.0.1:6060")
  if err != nil {
    // wrap the err or log why the listen failed
    return err
  }

  mux := http.NewServeMux()
  mux.Handle("/debug/pprof/", pprof.Index)
  mux.Handle("/debug/pprof/cmdline", pprof.CmdLine)
  mux.Handle("/debug/pprof/profile", pprof.Profile)
  mux.Handle("/debug/pprof/symbol", pprof.Symbol)
  mux.Handle("/debug/pprof/trace", pprof.Trace)

  go func() {
    if err := http.Serve(l, mux); err != nil {
      cancel()
      // log why we shut down the context
      return err
    }
  }()

  // If you want something semi-synchronous, sleep here for a fraction of a second

  return nil
}
Tajuanatak answered 13/1, 2020 at 8:11 Comment(1)
What exactly is this doing?Rysler
S
2

Reproducible example when you do not want your main server to be run in a separate goroutine:

main.go:

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "sync"
    "time"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
        // wait for 10 seconds before sending OK
        time.Sleep(10 * time.Second)
        _, _ = w.Write([]byte("OK\n"))
    })
    server := &http.Server{Addr: ":3333", Handler: nil}

    // Creating a waiting group that waits until the graceful shutdown procedure is done
    var wg sync.WaitGroup
    wg.Add(1)

    // This goroutine is running in parallels to the main one
    go func() {
        // creating a channel to listen for signals, like SIGINT
        stop := make(chan os.Signal, 1)
        // subscribing to interruption signals
        signal.Notify(stop, os.Interrupt)
        // this blocks until the signal is received
        <-stop
        // initiating the shutdown
        err := server.Shutdown(context.Background())
        // can't do much here except for logging any errors
        if err != nil {
            log.Printf("error during shutdown: %v\n", err)
        }
        // notifying the main goroutine that we are done
        wg.Done()
    }()

    log.Println("listening on port 3333...")
    err := server.ListenAndServe()
    if err == http.ErrServerClosed { // graceful shutdown
        log.Println("commencing server shutdown...")
        wg.Wait()
        log.Println("server was gracefully shut down.")
    } else if err != nil {
        log.Printf("server error: %v\n", err)
    }
}

Open two terminals. In the first run the app, in the second one run curl localhost:3333, then quickly switch to the first one and try to stop the app with CTRL+C

The output should be:

2021/03/12 13:39:49 listening on port 3333...
2021/03/12 13:39:50 user initiated a request
2021/03/12 13:39:54 commencing server shutdown...
2021/03/12 13:40:00 user request is fulfilled
2021/03/12 13:40:01 server was gracefully shut down.
Sorcim answered 12/3, 2021 at 21:43 Comment(0)
A
0

I created a module which implements (graceful) stopping of Go HTTP servers: https://github.com/pseidemann/finish

This removes the need of the boilerplate presented in the other answers.

Artemisia answered 19/8, 2022 at 11:15 Comment(0)
B
-9

What I've done for such cases where the application is just the server and performing no other function is install an http.HandleFunc for a pattern like /shutdown. Something like

http.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
    if <credentials check passes> {
        // - Turn on mechanism to reject incoming requests.
        // - Block until "in-flight" requests complete.
        // - Release resources, both internal and external.
        // - Perform all other cleanup procedures thought necessary
        //   for this to be called a "graceful shutdown".
        fmt.Fprint(w, "Goodbye!\n")
        os.Exit(0)
    }
})

It does not require 1.8. But if 1.8 is available, then that solution can be embedded here instead of the os.Exit(0) call if desirable, I believe.

The code to perform all of that cleanup work is left as an exercise for the reader.

Extra credit if you can say where that cleanup code might be most reasonably be placed, for I would not recommend doing it here, and how this endpoint hit should cause the invocation that code.

More extra credit if you can say where that os.exit(0) call (or whatever process exit you choose to use), given here for illustrative purposes only, would be most reasonably placed.

Yet even more extra credit if you can explain why this mechanism of HTTP server process signaling should be considered above all other such mechanisms thought workable in this case.

Buttonhole answered 29/4, 2017 at 3:5 Comment(3)
Of course, I answered the question as asked without any further assumptions about the nature of the problem, and in particular, no assumptions about any given production environment. But for my own edification, @MarcinBilski, exactly what requirements would render this solution not a good fit for any environment, production or otherwise?Buttonhole
Meant it more tongue-in-cheek than anything because it's clear you wouldn't have a /shutdown handler in a production app. :) Anything goes for internal tooling I guess. That aside, there are ways to gracefully shut down the server so it doesn't suddenly drop connections or crash mid-way through a database transaction or, worse, while writing to disk etc.Buckley
Certainly, it can't be the case that the down voters are unimaginative. It must be that I assume too much imagination. I've updated the response (including the example) to correct my error.Buttonhole

© 2022 - 2024 — McMap. All rights reserved.