eliben/code-for-blog

Graceful shutdown of TCP server

robertgates55 opened this issue · 1 comments

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?

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