Skip to content

Instantly share code, notes, and snippets.

@rcrowley
Last active March 1, 2023 16:06
Show Gist options
  • Star 76 You must be signed in to star a gist
  • Fork 27 You must be signed in to fork a gist
  • Save rcrowley/5474430 to your computer and use it in GitHub Desktop.
Save rcrowley/5474430 to your computer and use it in GitHub Desktop.
Graceful stop in Go
package main
import (
"log"
"net"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
// An uninteresting service.
type Service struct {
ch chan bool
waitGroup *sync.WaitGroup
}
// Make a new Service.
func NewService() *Service {
s := &Service{
ch: make(chan bool),
waitGroup: &sync.WaitGroup{},
}
s.waitGroup.Add(1)
return s
}
// Accept connections and spawn a goroutine to serve each one. Stop listening
// if anything is received on the service's channel.
func (s *Service) Serve(listener *net.TCPListener) {
defer s.waitGroup.Done()
for {
select {
case <-s.ch:
log.Println("stopping listening on", listener.Addr())
listener.Close()
return
default:
}
listener.SetDeadline(time.Now().Add(1e9))
conn, err := listener.AcceptTCP()
if nil != err {
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
continue
}
log.Println(err)
}
log.Println(conn.RemoteAddr(), "connected")
s.waitGroup.Add(1)
go s.serve(conn)
}
}
// Stop the service by closing the service's channel. Block until the service
// is really stopped.
func (s *Service) Stop() {
close(s.ch)
s.waitGroup.Wait()
}
// Serve a connection by reading and writing what was read. That's right, this
// is an echo service. Stop reading and writing if anything is received on the
// service's channel but only after writing what was read.
func (s *Service) serve(conn *net.TCPConn) {
defer conn.Close()
defer s.waitGroup.Done()
for {
select {
case <-s.ch:
log.Println("disconnecting", conn.RemoteAddr())
return
default:
}
conn.SetDeadline(time.Now().Add(1e9))
buf := make([]byte, 4096)
if _, err := conn.Read(buf); nil != err {
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
continue
}
log.Println(err)
return
}
if _, err := conn.Write(buf); nil != err {
log.Println(err)
return
}
}
}
func main() {
// Listen on 127.0.0.1:48879. That's my favorite port number because in
// hex 48879 is 0xBEEF.
laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:48879")
if nil != err {
log.Fatalln(err)
}
listener, err := net.ListenTCP("tcp", laddr)
if nil != err {
log.Fatalln(err)
}
log.Println("listening on", listener.Addr())
// Make a new service and send it into the background.
service := NewService()
go service.Serve(listener)
// Handle SIGINT and SIGTERM.
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
log.Println(<-ch)
// Stop the service gracefully.
service.Stop()
}
@rnapier
Copy link

rnapier commented Apr 27, 2014

Thanks for the ideas. Rather than polling on s, could the service just call Close() on the net.listener to mean "stop?" (In the current code, the Service doesn't keep track of the listener, but it could easily store that.)

@mgenov
Copy link

mgenov commented Mar 15, 2015

@luiscarrasco
Copy link

Thanks this helped me a lot.

@emersion
Copy link

... Or just use Listener.Close()

@SavostinVladimir
Copy link

Hi! Thanks a lot! Is there way to gracefully stop read from connection?

This is my solution, but I receive err:="read tcp ip1->ip2: use of closed network connection":

func (m *Proto) readRoutine(conn *net.TCPConn, stopRead chan bool) {
	//start to read in goroutine and send data to the chan
	data := make(chan interface{})
	defer close(data)
	go func() {
		for {
			d, err := conn.read()
                        if err != nil {
				fmt.Println(err)
			}
			if d == nil {
				return
			}
			data <- d
		}
	}()

	//listen stop and data channels and do stuff
	for {
		select {
		case <-stopRead:
			return
		case d := <-data:
			process( d)
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment