I’m unable to get a pipe state in Go (1.5).
While writing on a mkfifo
created pipe, I try to get the state of this output pipe:
- using the
Write
return statusEPIPE
- using the
Write
return statusEPIPE
andsignal.Ignore
on SIGPIPE (just in case) - using
signal.Notify
on SIGPIPE
I can see that:
EPIPE
is never returned- when I use
kill -13
, the signal handler is called: “Got signal: broken pipe” - when I
ctrl-c
the reader, the signal handler is not called and my program exits with output: “signal: broken pipe”
Would you, please, indicate my error ?
// tee.go package main import ( "fmt" "os" "os/signal" "syscall" sys "golang.org/x/sys/unix" ) // wait for a signal and print it func handleSignal(csig chan os.Signal) { for { fmt.Println("Wait signal") s := <-csig fmt.Println("Got signal:", s) } } func main() { csig := make(chan os.Signal, 1) // `kill -13` outputs "Got signal: broken pipe" => ok signal.Notify(csig, sys.SIGPIPE) // OR disable the previous `Notify` just to be sure ? // maybe it will help to get the EPIPE error status on `Write` ? //signal.Ignore(sys.SIGPIPE) go handleSignal(csig) // open previously created named pipe (`mkfifo /tmp/test`) pipe, _ := os.OpenFile("/tmp/test", os.O_WRONLY, 0) for { _, err := pipe.Write([]byte("foon")) if err == syscall.EPIPE { // never called => ko fmt.Println("EPIPE error") } } }
Note: as a simple Go exercise, I try to implement a command which almost acts like tee -a <a_file>
(print stdin to stdout and <a_file>
) with the following specificity: non blocking write on a named pipe and optional reader.
Advertisement
Answer
The error returned is not a plain syscall.Error
but instead wrapped within *os.PathError
as illustrated with the following variation of your code:
package main import ( "fmt" "os" "syscall" ) func main() { // open previously created named pipe (`mkfifo /tmp/test`) pipe, _ := os.OpenFile("/tmp/test", os.O_WRONLY, 0) for { n, err := pipe.Write([]byte("foon")) fmt.Printf("write: n=%v, err=(%T) %[2]vn", n, err) if err == syscall.EPIPE { fmt.Println("EPIPE error") } else if perr, ok := err.(*os.PathError); ok { fmt.Printf("op: %q; path=%q; err=(%T) %[3]qn", perr.Op, perr.Path, perr.Err) if perr.Err == syscall.EPIPE { fmt.Println("os.PathError.Err is EPIPE") } } } }
Running this after doing mkfifo /tmp/test; head /tmp/test
elsewhere gives me:
write: n=4, err=(<nil>) <nil> [… repeated nine more times, as the head command reads ten lines …] write: n=0, err=(*os.PathError) write /tmp/test: broken pipe op: "write"; path="/tmp/test"; err=(syscall.Errno) "broken pipe" os.PathError.Err is EPIPE [… above three lines repeated nine more times …] signal: broken pipe Exit 1
After returning ten pipe errors on an individual file the Go runtine stops catching/blocking SIGPIPE and lets it through to your program killing it. I don’t believe the Go runtime lets you catch or ignore SIGPIPE as it uses that signal itself internally.
So two things: one, to look for syscall.EPIPE
you need to check for *os.PathError
as shown and two, don’t continue on when err != nil
when you haven’t actually handled the error.
I don’t know the details of why Go handles SIGPIPE in this way; perhaps a search of Go’s bug tracker and/or the go-nuts list may help answer that.
With Go 1.5’s additions to the os/signal
package doing signal.Reset(syscall.SIGPIPE)
(before any signal.Notify
calls) changes this behaviour.