UnixListener.Closeでソケットファイルが消えて困っている

具体的には https://github.com/golang/go/blob/f78a4c84ac8ed44aaf331989aa32e40081fd8f13/src/net/unixsock_posix.go#L339 の実装。

[Go言語でGraceful Restartをする](http://shogo82148.github.io/blog/2015/05/03/golang-graceful-restart/) を参考にgoのhttpサーバをServer::Starter経由で起動させたいのだけど、この挙動のお蔭でunix domain socketを使った場合に困っている。

```go:main.go
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/braintree/manners"
	"github.com/lestrrat/go-server-starter/listener"
)

func main() {
	go func() {
		sigchan := make(chan os.Signal, 1)
		signal.Notify(sigchan, syscall.SIGTERM)
		<-sigchan
		manners.Close()
	}()

	l, err := listener.ListenAll()
	if err != nil {
		log.Fatalf("Failed to liten: %v", err)
	}

	http.Handle("/", handler)
	err = manners.Serve(l[0], nil)
	if err != nil {
		log.Fatalf("Failed to Serve: %v", err)
	}
}

var handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "OK")
})
```

```
# サーバ起動
$ plenv exec carton exec -- start_server --path=/tmp/ok.sock -- main
start_server (pid:17571) starting now...
starting new worker 17611

# 起動状態の確認
$ ls -la /tmp/ok.sock
srwxrwxrwx 1 hiratara hiratara 0 11月  6 23:53 /tmp/ok.sock
$ echo -en 'GET / HTTP/1.0\r\n\r\n' | socat stdio /tmp/ok.sock
HTTP/1.0 200 OK
Date: Fri, 06 Nov 2015 14:53:45 GMT
Content-Length: 3
Content-Type: text/plain; charset=utf-8

OK

# 再起動
$ kill -HUP 17571

# サーバ側
received HUP, spawning a new worker
starting new worker 17633
new worker is now running, sending TERM to old workers:17611
killing old workers
old worker 17611 died, status:0

# サーバ状態の確認
$ ls -la /tmp/ok.sock
ls: /tmp/ok.sock にアクセスできません: そのようなファイルやディレクトリはありません
$ echo -en 'GET / HTTP/1.0\r\n\r\n' | socat stdio /tmp/ok.sock
```

以下のように `Close` を呼ぶ間だけディレクトリのパーミッションを奪うという最悪なラッパーを作って `Wrap(l[0])` するくらいしか思いついていない状態。一応望む挙動にはなったけど。

```go
type wrappedListener struct {
	*net.UnixListener
}

func Wrap(l net.Listener) net.Listener {
	switch ll := l.(type) {
	case *net.UnixListener:
		return &wrappedListener{UnixListener: ll}
	default:
		return ll
	}
}

func (wl *wrappedListener) Close() (err error) {
	sockPath := wl.Addr().String()
	sockDir := path.Dir(sockPath)

	info, err := os.Stat(sockDir)
	if err != nil {
		log.Fatalf("Failed to stat: %v", err)
	}

	origMode := info.Mode()
	mode := (origMode & ^os.ModePerm) | (origMode & 0555)

	err = os.Chmod(sockDir, mode)
	if err != nil {
		log.Fatalf("Failed to chmod: %v", err)
	}

	defer func() {
		err := os.Chmod(sockDir, origMode)
		if err != nil {
			log.Fatalf("Failed to chmod: %v", err)
		}
	}()

	return wl.UnixListener.Close()
}
```

過去に同じことを考えた人は https://github.com/coreos/go-systemd/issues/71#issuecomment-73351251 に居たっぽい。

もう一つ考えた方法はそもそも `Close()` 呼ばずにexitするで、[go nutsではそうしろ](https://groups.google.com/d/msg/golang-nuts/UtBR4IfgaEw/EagZfzRytOQJ)って言ってる。けど、`Close()`が呼べないと`Accept()`のループから処理が戻らないので https://github.com/braintree/manners/blob/master/server.go#L216 でブロックしたままになる。かといって、 `manners` のキモは https://github.com/braintree/manners/blob/master/server.go#L221 で全リクエストの処理が終わるのを待つことのはずなので、この行を抜ける前に別のところで勝手に `exit` するのはまずい気がする。

## UnixListener.Close周りのコードの追っかけ

`start_server` は go のプロセスにファイルディスクリプタを渡してくるので、それをlistenする。 https://github.com/lestrrat/go-server-starter/blob/master/listener/listener.go#L82

おっかけていくと `UnixListener` 型を作っていることがわかる。 https://github.com/golang/go/blob/f78a4c84ac8ed44aaf331989aa32e40081fd8f13/src/net/file_unix.go#L94

`UnixListener` の `Close` ではファイルを消している。 https://github.com/golang/go/blob/f78a4c84ac8ed44aaf331989aa32e40081fd8f13/src/net/unixsock_posix.go#L339

`l.fd.Close()` だけを呼びたいのだがそのような口がなく、 `l.fd` はプライベートなので引っ張り出せなくて悲しい感じ。

## 【追記】

> [@kazuho さん](http://b.hatena.ne.jp/entry/270776675/comment/kazuhooku)
> その設計自体がPOSIX違反>「Close()が呼べないとAccept()のループから処理が戻らない」
> [@tanaka_akr さん](https://twitter.com/tanaka_akr/status/663885240385929217)
> いくつかの OS で試した結果、DragonFly BSD では close(2) でも shutdown(2) でも accept(2) しているスレッドは起きなかったので真面目にパイプを作ったという思い出があります。(Ruby の WEBrick)

POSIX では `Close()` を呼んだからといって `Accept()` が制御を戻してくれる保証はないといことでしょうか。知りませんでした。となると、 [manners側の実装方針](https://github.com/braintree/manners/blob/master/server.go#L216) に問題があると言えそうです。では一般的に正しい手法はどうなのかというのは、わかってないので調べて分かったら追記します。

> [@kazuho さん](http://b.hatena.ne.jp/entry/270776675/comment/kazuhooku)
> 動けばなんでもいいなら accept fdに普通のファイルデスクリプタをdup2するという手もある

こちらもgolangでどうすればっていうのが見えてないのでわかったら追記しますが、せっかく教えていただいたので一応目立つように引用しておきます。