0
1

signal: broken pipe が解消されないときはGoのバージョンも関係してるかも

Last updated at Posted at 2019-12-08

こんにちはこんにちは
本記事は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)
			}
		}
	}
}

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1