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.