1

I'm having the following server code, which listens via unix domain socket

package main

import (
    "log"
    "net"
    "os"
    "os/signal"
    "syscall"
)

func echoServer(c net.Conn) {
    for {
        buf := make([]byte, 512)
        nr, err := c.Read(buf)
        if err != nil {
            return
        }

        data := buf[0:nr]
        println("Server got:", string(data))
        _, err = c.Write(data)
        if err != nil {
            log.Fatal("Writing client error: ", err)
        }
    }
}

func main() {
    log.Println("Starting echo server")
    ln, err := net.Listen("unix", "/tmp/go.sock")
    if err != nil {
        log.Fatal("Listen error: ", err)
    }

    sigc := make(chan os.Signal, 1)
    signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
    go func(ln net.Listener, c chan os.Signal) {
        sig := <-c
        log.Printf("Caught signal %s: shutting down.", sig)
        ln.Close()
        os.Exit(0)
    }(ln, sigc)

    for {
        fd, err := ln.Accept()
        if err != nil {
            log.Fatal("Accept error: ", err)
        }

        go echoServer(fd)
    }
}

When I close it using Ctrl+C, then the signal is captured and the socket is closed. When I rerun the program, everything works fine.

However, if the running process is abruptly killed, and if the program is restarted, the listen fails with the error Listen error: listen unix /tmp/go.sock: bind: address already in use

How to graciously handle this?

The reason why I ask this is: I know that abruptly killing is not the normal method, but my program shall be launched automatically as a daemon and if my daemon is restarted, I want to be able to listen to the socket again without this error.

It could also be because of a prior instance running, which I understand. The question here is how to programmatically identify in Go and handle this situation. As pointed in the answer here, one can use SO_REUSEADDR in C programs. Is there such a possibility in Go? Also, how do C programs handle this multiple instance problem.

Community
  • 1
  • 1
J Aamish
  • 522
  • 7
  • 12
  • Servers don't connect. Clients connect. Servers listen and accept connections. If you get 'address already in use when binding, the address is already in use, possibly by a prior instance of your program, possibly because 'abruptly killing' is not the way to terminate it. – user207421 May 14 '17 at 10:29
  • @EJP Wow.. -1 for a typo, where I had written `connect` instead of `listen`. That is funny that you say that abruptly killing is not the way to terminate it. I know that. However, when a customer who is using my program somehow kills it, I want to think how to handle it graceful! In C, I can use reuse socket, but how to do that in Go in the question here. Think before you give negative points to a question! – J Aamish May 14 '17 at 10:31
  • 2
    SO_REUSEADDR isn't relevant here (see http://stackoverflow.com/questions/15716302/so-reuseaddr-and-af-unix). Delete the file if you don't need it and want to bind to that address again. – JimB May 14 '17 at 11:53
  • See this [related question](http://stackoverflow.com/questions/16681944/how-to-reliably-unlink-a-unix-domain-socket-in-go-programming-language). In short, also handle `os.Kill` and `syscall.SIGTERM` signal in `signal.Notify`. – putu May 14 '17 at 12:08
  • @putu you can't handle SIGKILL. – JimB May 14 '17 at 12:58
  • @JimB yes, you're right, my mistake. As you mentioned simply deleting the file `/tmp/go.sock` before calling `net.Listen` should fix the problem. – putu May 14 '17 at 13:18
  • 2
    This answer explains how to detect if a working instance is listening, and its question has more details on why to delete the unix domain socket: http://stackoverflow.com/a/13719866/7496656 – Jan Zerebecki May 14 '17 at 19:54

1 Answers1

0

You need to catch the signal and cleanup; some example code:

func HandleSIGINTKILL() chan os.Signal {
    sig := make(chan os.Signal, 1)

    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

    return sig
}

...
go func() {
    <-HandleSIGINTKILL()

    log.Info("Received termination signal")
    // Cleanup code here

    os.Exit(0)
}()

This will of course not work if you kill -9 the process; you will need to manually remove the socket (or have your init system do it for you).