How do I stop a Listening server in Go

14,069

Solution 1

Check some "is it time to stop" flag in your loop right after the accept() call, then flip it from your main, then connect to your listening port to get server socket "un-stuck". This is very similar to the old "self-pipe trick".

Solution 2

I would handle this by using es.done to send a signal before it closes the connection. In addition to the following code you'd need to create es.done with make(chan bool, 1) so that we can put a single value in it without blocking.

// Listen for incoming connections
func (es *EchoServer) serve() {
  for {
    conn, err := es.listen.Accept()
    if err != nil {
      select {
      case <-es.done:
        // If we called stop() then there will be a value in es.done, so
        // we'll get here and we can exit without showing the error.
      default:
        log.Printf("Accept failed: %v", err)
      }
      return
    }
    go es.respond(conn.(*net.TCPConn))
  }
}

// Stop the server by closing the listening listen
func (es *EchoServer) stop() {
  es.done <- true   // We can advance past this because we gave it buffer of 1
  es.listen.Close() // Now it the Accept will have an error above
}

Solution 3

Something among these lines might work in this case, I hope:

// Listen for incoming connections
func (es *EchoServer) serve() {
        for {
                conn, err := es.listen.Accept()
                if err != nil {
                    if x, ok := err.(*net.OpError); ok && x.Op == "accept" { // We're done
                            log.Print("Stoping")
                            break
                    }

                    log.Printf("Accept failed: %v", err)
                    continue
                }
                go es.respond(conn.(*net.TCPConn))
        }
        es.done <- true
}
Share:
14,069

Related videos on Youtube

Nick Craig-Wood
Author by

Nick Craig-Wood

Just discovered what is supposed to be in that grey box in my user page ;-) For more about me see My web page My twitter account My Git hub account ...and no that isn't really my birthday, but it is the birth date of something important!

Updated on June 12, 2022

Comments

  • Nick Craig-Wood
    Nick Craig-Wood almost 2 years

    I've been trying to find a way to stop a listening server in Go gracefully. Because listen.Accept blocks it is necessary to close the listening socket to signal the end, but I can't tell that error apart from any other errors as the relevant error isn't exported.

    Can I do better than this? See FIXME in the code below in serve()

    package main
    
    import (
        "io"
        "log"
        "net"
        "time"
    )
    
    // Echo server struct
    type EchoServer struct {
        listen net.Listener
        done   chan bool
    }
    
    // Respond to incoming connection
    //
    // Write the address connected to then echo
    func (es *EchoServer) respond(remote *net.TCPConn) {
        defer remote.Close()
        _, err := io.Copy(remote, remote)
        if err != nil {
            log.Printf("Error: %s", err)
        }
    }
    
    // Listen for incoming connections
    func (es *EchoServer) serve() {
        for {
            conn, err := es.listen.Accept()
            // FIXME I'd like to detect "use of closed network connection" here
            // FIXME but it isn't exported from net
            if err != nil {
                log.Printf("Accept failed: %v", err)
                break
            }
            go es.respond(conn.(*net.TCPConn))
        }
        es.done <- true
    }
    
    // Stop the server by closing the listening listen
    func (es *EchoServer) stop() {
        es.listen.Close()
        <-es.done
    }
    
    // Make a new echo server
    func NewEchoServer(address string) *EchoServer {
        listen, err := net.Listen("tcp", address)
        if err != nil {
            log.Fatalf("Failed to open listening socket: %s", err)
        }
        es := &EchoServer{
            listen: listen,
            done:   make(chan bool),
        }
        go es.serve()
        return es
    }
    
    // Main
    func main() {
        log.Println("Starting echo server")
        es := NewEchoServer("127.0.0.1:18081")
        // Run the server for 1 second
        time.Sleep(1 * time.Second)
        // Close the server
        log.Println("Stopping echo server")
        es.stop()
    }
    

    This prints

    2012/11/16 12:53:35 Starting echo server
    2012/11/16 12:53:36 Stopping echo server
    2012/11/16 12:53:36 Accept failed: accept tcp 127.0.0.1:18081: use of closed network connection
    

    I'd like to hide the Accept failed message, but obviously I don't want to mask other errors Accept can report. I could of course look in the error test for use of closed network connection but that would be really ugly. I could set a flag saying I'm about to close and ignore errors if that was set I suppose - Is there a better way?

  • Nick Craig-Wood
    Nick Craig-Wood over 11 years
    A good idea thanks! I'm not sure it would tell apart an official stop from Syscall.Accept() returning an error (a recent example I've seen is the process running out of sockets) would it?
  • Nick Craig-Wood
    Nick Craig-Wood over 11 years
    That is a neat idea! There is a race condition with real connections coming in though which you'd need to work around.
  • zzzz
    zzzz over 11 years
    Dunno, I would have to try/experiment with it.
  • Nikolai Fetissov
    Nikolai Fetissov over 11 years
    Yes, true. You can try an inverse of that - always have that single internal connection from the start and use it as a control channel.
  • Nick Craig-Wood
    Nick Craig-Wood over 11 years
    Hmm, nice idea. I think a bool would do just as well as using the channel though which is pretty much the solution I came up with. You still need the channel though to synchronise the stop with serve so you know when it has stopped.
  • Dustin
    Dustin over 11 years
    Don't bother buffering the channel or sending a message down the channel. Just close it.
  • Setomidor
    Setomidor about 8 years
    Exiting the entire process is not a valid way to close a server!
  • luben
    luben over 4 years
    Great example, but shouldn't the x.Op be compared to close, e.g. && x.Op == "close" If we stop the listener by using listener.Close(), it seems to put close inside the error operation. godoc.org/net#TCPListener.Close
  • Prisacari Dmitrii
    Prisacari Dmitrii over 2 years
    Great and simple!