Skip to content

net/http: unix socket does not get removed when application exits, which makes restart fail #70985

Closed as not planned
@XANi

Description

@XANi

Go version

go version go1.23.4 linux/amd64

Output of go env in your module/workspace:

env really doesn't matter here, this bug have years.

I initially thought it was Gin bug ( https://github.com/gin-gonic/gin/issues/3817 ) but I digged deeper

What did you do?

Running this code

func main() {
	u, err := net.Listen("unix", "sock_raw")
	fmt.Println(err)
	if err == nil {
		defer u.Close()
	}
}

over and over will work every time unless killed by SIGKILL because u.Close() cleans up the socket.

but adding http.Serve in the mix:

package main

import (
	"fmt"
	"net"
	"net/http"
)

type H struct{}

func (h *H) ServeHTTP(w http.ResponseWriter, r *http.Request) {

}

func main() {
	u, err := net.Listen("unix", "sock_http")
	fmt.Println(err)
	if err == nil {
		defer u.Close()
		fmt.Println("press C-c and run me again")
		fmt.Println(http.Serve(u, &H{}))
	}

}

will fail after first run because of leftover socket that now for some reason is not cleared by u.Close(). Even if cleanup is added manually by defer os.Remove()

...
func main() {
	u, err := net.Listen("unix", "sock_http")
	defer os.Remove("sock_http") # <-- here
	fmt.Println(err)
	if err == nil {
		defer u.Close()
		fmt.Println("press C-c and run me again")
		fmt.Println(http.Serve(u, &H{}))
	}
}

the result is still the same:

-> ᛯ go run main.go                                                                                                                                                     1
<nil>
press C-c and run me again
^Csignal: interrupt
-> ᛯ go run main.go                                                                                                                                                     1
listen unix sock_http: bind: address already in use
[20:44:53] ^ [/tmp/g] 
-> ᛯ 

I even tried adding

		defer func() {
			c := exec.Command("/bin/rm", "sock_http")
			c.Run()
		}()

but that didn't remove it either. Also tried adding signal handler so it gets clean os.Exit:

package main

import (
	"fmt"
	"net"
	"net/http"
	"os"
	"os/signal"
)

func main() {

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			fmt.Println("exiting")
			os.Exit(1)
		}
	}()
	u, err := net.Listen("unix", "sock_http")
	fmt.Println(err)
	if err == nil {
		defer u.Close()
		fmt.Println("press C-c and run me again")
		fmt.Println(http.Serve(u, nil))
	}

with same result:

-> ᛯ go run main.go
<nil>
press C-c and run me again
^Cexiting
exit status 1
-> ᛯ go run main.go                                                                                                                                                     1
listen unix sock_http: bind: address already in use

What did you see happen?

Socket not being removed when http.Serve uses it

What did you expect to see?

I'd expect one of 2 things to happen:

  • net.Listen user pre-created socket if it can, which allows to, for example, set the permissions to the socket before listening on it which might desirable security-wise
  • the socket is correctly cleared if removed when http.Serve is using it

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions