How to stop http.ListenAndServe()

Go

Go Problem Overview


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?

Go Solutions


Solution 1 - Go

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")
}

Solution 2 - Go

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

Solution 3 - Go

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

Solution 4 - Go

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)

Solution 5 - Go

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.

Solution 6 - Go

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()

Solution 7 - Go

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
}

Solution 8 - Go

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.

Solution 9 - Go

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjimView Question on Stackoverflow
Solution 1 - Gojoonas.fiView Answer on Stackoverflow
Solution 2 - GoJohn S PerayilView Answer on Stackoverflow
Solution 3 - Goyo.ian.gView Answer on Stackoverflow
Solution 4 - GoUvelichitelView Answer on Stackoverflow
Solution 5 - GojuzView Answer on Stackoverflow
Solution 6 - GoLukas LukacView Answer on Stackoverflow
Solution 7 - GoSeanView Answer on Stackoverflow
Solution 8 - GoIvanDView Answer on Stackoverflow
Solution 9 - Gogreg.carterView Answer on Stackoverflow