syscll
syscll

Reputation: 1031

Gracefully shutting down multiple servers

I have an application that runs a basic HTTP server and also accepts connections over TCP.

Basic pseudo code is as follows:

package main

import (
    "log"
    "net"
    "net/http"
)

func main() {
    // create serve HTTP server.
    serveSvr := http.NewServeMux()
    serveSvr.HandleFunc("/", handler())

    // create server error channel
    svrErr := make(chan error)

    // start HTTP server.
    go func() {
        svrErr <- http.ListenAndServe(":8080", serveSvr)
    }()

    // start TCP server
    go func() {
        lnr, err := net.Listen("tcp", ":1111")
        if err != nil {
            svrErr <- err
            return
        }
        defer lnr.Close()

        for {
            conn, err := lnr.Accept()
            if err != nil {
                log.Printf("connection error: %v", err)
                continue
            }
            // code to handle each connection
        }
    }()

    select {
    case err := <-svrErr:
        log.Print(err)
    }
}

I run both servers in separate goroutines and I need a way to gracefully shut them both down if either of them fail. For example; if the HTTP server errors, how would I go back and shutdown the TCP server/perform any cleanup?

Upvotes: 1

Views: 1123

Answers (1)

Mr_Pink
Mr_Pink

Reputation: 109347

Start by keeping a reference to the http server and the tcp listener so that you can later close them.

Create separate error channels so you know which path returned the error, and buffer them so that a send can always complete.

To make sure that whatever cleanup you want to attempt is complete before you exit, you can add a WaitGroup to the server goroutines.

I simple extension of your example might look like:

var wg sync.WaitGroup

// create  HTTP server.
serveSvr := http.NewServeMux()
serveSvr.HandleFunc("/", handler())
server := &http.Server{Addr: ":8080", Handler: serveSvr}

// create http server error channel
httpErr := make(chan error, 1)

// start HTTP server.
wg.Add(1)
go func() {
    defer wg.Done()
    httpErr <- server.ListenAndServe()
    // http cleanup
}()

tcpErr := make(chan error, 1)
listener, err := net.Listen("tcp", ":1111")
if err != nil {
    tcpErr <- err
} else {
    // start TCP server
    wg.Add(1)
    go func() {
        defer wg.Done()
        defer listener.Close()
        for {
            conn, err := listener.Accept()
            if err != nil {
                if ne, ok := err.(net.Error); ok && ne.Temporary() {
                    // temp error, wait and continue
                    continue
                }
                tcpErr <- err

                // cleanup TCP
                return
            }

            // code to handle each connection
        }
    }()
}
select {
case err := <-httpErr:
    // handle http error and close tcp listen
    if listener != nil {
        listener.Close()
    }
case err := <-tcpErr:
    // handle tcp error and close http server
    server.Close()
}

// you may also want to receive the error from the server
// you shutdown to log

// wait for any final cleanup to finish
wg.Wait()

Upvotes: 1

Related Questions