Tawesoft Logo

tawesoft.co.uk/go/grace example: multiportserver.go

// Start HTTP servers on multiple ports with graceful shutdown
package main

import (
    "context"
    "fmt"
    "net"
    "net/http"
    "os"
    "syscall"
    "time"

    "tawesoft.co.uk/go/grace"
)

// Implement grace.Process
type HttpServer struct {
    Addr string // e.g. ":8080"
    Srv *http.Server
    Ln net.Listener
}

func (s *HttpServer) Start(done func()) error {
    // non-async startup
    s.Srv = &http.Server{}
    ln, err := net.Listen("tcp", s.Addr)
    if err != nil { return err }
    s.Ln = ln

    // async server
    go func() {
        defer done()

        fmt.Printf("Serve %s\n", s.Addr)

        // blocks until Shutdown
        err := s.Srv.Serve(s.Ln)

        time.Sleep(50 * time.Millisecond)

        // ErrServerClosed on graceful close
        if err == http.ErrServerClosed {
            fmt.Printf("server closed normally\n")
        } else {
            fmt.Printf("server error: %v\n", err)
        }
    }()

    return nil
}

func (s *HttpServer) Stop(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, time.Second * 1)
    defer cancel()
    fmt.Printf("Shutdown %s\n", s.Addr)
    return s.Srv.Shutdown(ctx) // closes s.Ln
}

func main() {
    servers := []grace.Process{
        &HttpServer{Addr: ":8081"},
        &HttpServer{Addr: ":8082"},
        &HttpServer{Addr: ":8083"},
        &HttpServer{Addr: ":8084"},
        // &HttpServer{Addr: ":8084"}, // uncomment this to try out startup errors
        &HttpServer{Addr: ":8085"},
    }

    signals := []os.Signal{
        syscall.SIGINT,
        syscall.SIGKILL,
        syscall.SIGTERM,
    }

    // blocks until cancelled, interrupted, terminated, or killed, until
    // all servers have shutdown, and all start functions have marked
    // themselves as completely `done()` (e.g. so they have time to clean up)
    signal, errs, err := grace.Run(context.Background(), servers, signals)

    if err != nil {
        fmt.Printf("Startup error: %v\n", err)
    } else {
        fmt.Printf("Shutdown complete. Recieved %s signal\n", signal)
    }

    if len(errs) > 0 {
        fmt.Printf("Shutdown errors: %v\n", errs)
    }
}