How to reliably unlink() a Unix domain socket in Go programming language

James Dunne picture James Dunne · May 22, 2013 · Viewed 7.6k times · Source

I have a Go program hosting a simple HTTP service on localhost:8080 so I can connect my public nginx host to it via the proxy_pass directive, as a reverse proxy to serve part of my site's requests. This is all working great, no problems there.

I want to convert the Go program to host the HTTP service on a Unix domain socket instead of a local TCP socket for improved security and to reduce the unnecessary protocol overhead of TCP.

PROBLEM: The problem is that Unix domain sockets cannot be reused once they are bind() to, even after program termination. The second time (and every time after) I run the Go program it exits with a fatal error "address already in use".

Common practice is to unlink() Unix domain sockets (i.e. remove the file) when the server shuts down. However, this is tricky in Go. My first attempt was to use the defer statement in my main func (see below), but it is not getting run if I interrupt the process with a signal like CTRL-C. I suppose this is to be expected. Disappointing, but not unexpected.

QUESTION: Is there a best practice on how to unlink() the socket when the server process shuts down (either gracefully or ungracefully)?

Here's part of my func main() that starts the server listening for reference:

// Create the HTTP server listening on the requested socket:
l, err := net.Listen("unix", "/tmp/mysocket")
if err != nil {
    log.Fatal(err)
} else {
    // Unix sockets must be unlink()ed before being reused again.
    // Unfortunately, this defer is not run when a signal is received, e.g. CTRL-C.
    defer func() {
        os.Remove("/tmp/mysocket")
    }()

    log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))
}

Answer

James Dunne picture James Dunne · May 22, 2013

Here is the complete solution I used. The code I posted in my question was a simplified version for clear demonstration purposes.

// Create the socket to listen on:
l, err := net.Listen(socketType, socketAddr)
if err != nil {
    log.Fatal(err)
    return
}

// Unix sockets must be unlink()ed before being reused again.

// Handle common process-killing signals so we can gracefully shut down:
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill, syscall.SIGTERM)
go func(c chan os.Signal) {
    // Wait for a SIGINT or SIGKILL:
    sig := <-c
    log.Printf("Caught signal %s: shutting down.", sig)
    // Stop listening (and unlink the socket if unix type):
    l.Close()
    // And we're done:
    os.Exit(0)
}(sigc)

// Start the HTTP server:
log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))

I sure hope this is good and effective Go code that would make the Go authors proud. It certainly looks so to me. If it is not, that would be embarrassing on my part. :)

For anyone curious, this is part of https://github.com/JamesDunne/go-index-html which is a simple HTTP directory listing generator with some extra features that web servers don't give you out of the box.