こんにちはこんにちは
本記事はsignal: broken pipe
について調べた記録。
自分がハマった環境は以下。
❯ go version
go version go1.11.4 darwin/amd64
次のようなコードを書き、パイプで別プロセスにつなげる。
パイプ先が終了するとsignal: broken pipe
と表示される。
package main
import (
"fmt"
)
func main() {
for {
fmt.Println("hello")
}
}
❯ go run main.go | head -n 1
hello
signal: broken pipe
この挙動自体はドキュメントに書いてある。
If the program has not called Notify to receive SIGPIPE signals, then the behavior depends on the file descriptor number. A write to a broken pipe on file descriptors 1 or 2 (standard output or standard error) will cause the program to exit with a SIGPIPE signal. A write to a broken pipe on some other file descriptor will take no action on the SIGPIPE signal, and the write will fail with an EPIPE error.
またsignal.Ignore()という関数もあるので、こちらを参考にしながらパイプ先が閉じてもsignal: broken pipe
を出さないようにしてみる。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
signal.Ignore(syscall.SIGPIPE)
for {
_, err := fmt.Println("hello")
if err != nil {
if e, ok := err.(*os.PathError); ok && e.Err == syscall.EPIPE {
break
} else {
panic(err)
}
}
}
}
❯ go run main.go | head -n 1
hello
signal: broken pipe
あれ?変わらない。
ぐぐってみると、次のIssueが見つかった。
runtime: signal.Ignore(syscall.SIGPIPE) does not work as documented · Issue #32386 · golang/go
どうやらSIGPIPEを無視する場合の考慮が漏れていた模様。
Go 1.13.4には上記修正が含まれているようなので、Goのバージョンを上げて再度試す。
❯ go version
go version go1.13.4 darwin/amd64
❯ go run main.go | head -n 1
hello
おけ。
おまけ(その1)
Go 1.13であれば errors.Is()が使えるため、もう少しシンプルに書ける。
_, err := fmt.Println("hello")
if err != nil {
if errors.Is(err, syscall.EPIPE) {
break
} else {
panic(err)
}
}
おまけ(その2)
Go 1.11.4ではどうしたらいいか?
signal.Notify()で受け無視することで、とりあえずの対処はできそう。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGPIPE)
go func() {
<-c // ignore
}()
for {
_, err := fmt.Println("hello")
if err != nil {
if e, ok := err.(*os.PathError); ok && e.Err == syscall.EPIPE {
break
} else {
panic(err)
}
}
}
}