eliben / code-for-blog

Code samples from my blog
The Unlicense
1.57k stars 744 forks source link

Graceful shutdown of TCP server #25

Closed robertgates55 closed 3 years ago

robertgates55 commented 3 years ago

Thanks for this - all sorts of great stuff in here. Obviously this isn't an issue, per se, but it felt like a decent way to ask the question!

I've been looking at implementing graceful shutdown for a project we're working on, but struggled to get your demo to run in the way I expected. I've created: shutdown.go:

// This version expects all clients to close their connections before it
// successfully returns from Stop().
//
// Eli Bendersky [https://eli.thegreenplace.net]
// This code is in the public domain.
package main

import (
"io"
"log"
"net"
"sync"
)

type Server struct {
    listener net.Listener
    quit     chan interface{}
    wg       sync.WaitGroup
}

func NewServer(addr string) *Server {
    s := &Server{
        quit: make(chan interface{}),
    }
    l, err := net.Listen("tcp", addr)
    if err != nil {
        log.Fatal(err)
    }
    s.listener = l
    s.wg.Add(1)
    go s.serve()
    return s
}

func (s *Server) Stop() {
    close(s.quit)
    s.listener.Close()
    s.wg.Wait()
}

func (s *Server) serve() {
    defer s.wg.Done()

    for {
        conn, err := s.listener.Accept()
        if err != nil {
            select {
            case <-s.quit:
                return
            default:
                log.Println("accept error", err)
            }
        } else {
            s.wg.Add(1)
            go func() {
                s.handleConection(conn)
                s.wg.Done()
            }()
        }
    }
}

func (s *Server) handleConection(conn net.Conn) {
    defer conn.Close()
    buf := make([]byte, 2048)
    for {
        n, err := conn.Read(buf)
        if err != nil && err != io.EOF {
            log.Println("read error", err)
            return
        }
        if n == 0 {
            return
        }
        log.Printf("received from %v: %s", conn.RemoteAddr(), string(buf[:n]))
    }
}

func init() {
    log.SetFlags(log.Ltime | log.Lmicroseconds)
}

func main() {
    log.Println("Starting")
    s := NewServer("0.0.0.0:1234")
    // do whatever here...
    log.Println("Whatever")

    log.Println("Stopping")
    s.Stop()
    log.Println("Stopped")
}

It's a direct copy/paste from this repo, with a main() function invoking it (And a couple of extra bits of logging).

>> go run shutdown.go

What I expected - I expected a blocking server, listening on :1234 What happens - it completes immediately:

➜ go run shutdown.go
19:34:44.492644 Starting
19:34:44.493001 Whatever
19:34:44.493002 Stopping
19:34:44.493036 Stopped
➜

What am I missing? What should the // do whatever here... actually contain if I just want a server listening until I SIGKILL the process? I presume I need something to block - but based on your description I thought that's what the code was doing?

eliben commented 3 years ago

You invoke s.Stop(), which tells the server to stop listening and shut down. I'm not sure what you expect to happen.