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()
}
@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