英文:
Getting a pipe status in Go
问题
我无法在Go(1.5)中获取管道状态。
在写入一个mkfifo
创建的管道时,我尝试获取该输出管道的状态:
- 使用
Write
返回状态EPIPE
- 使用
Write
返回状态EPIPE
和signal.Ignore
在SIGPIPE上(以防万一) - 使用
signal.Notify
在SIGPIPE上
我可以看到:
EPIPE
从未返回- 当我使用
kill -13
时,信号处理程序被调用:“Got signal: broken pipe” - 当我
ctrl-c
读取器时,信号处理程序不会被调用,我的程序退出并输出:“signal: broken pipe”
请问,你能指出我的错误吗?
// tee.go
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
sys "golang.org/x/sys/unix"
)
// 等待信号并打印它
func handleSignal(csig chan os.Signal) {
for {
fmt.Println("等待信号")
s := <-csig
fmt.Println("收到信号:", s)
}
}
func main() {
csig := make(chan os.Signal, 1)
// `kill -13` 输出 "Got signal: broken pipe" => 正常
signal.Notify(csig, sys.SIGPIPE)
// 或者禁用之前的 `Notify` 以确保?
// 或许这样可以帮助在 `Write` 上获取 EPIPE 错误状态?
//signal.Ignore(sys.SIGPIPE)
go handleSignal(csig)
// 打开之前创建的命名管道(`mkfifo /tmp/test`)
pipe, _ := os.OpenFile("/tmp/test", os.O_WRONLY, 0)
for {
_, err := pipe.Write([]byte("foo\n"))
if err == syscall.EPIPE {
// 从未调用 => 错误
fmt.Println("EPIPE 错误")
}
}
}
**注意:**作为一个简单的Go练习,我尝试实现一个类似于tee -a <a_file>
的命令(将stdin打印到stdout和<a_file>
),具有以下特点:在命名管道上进行非阻塞写入和可选读取器。
英文:
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("foo\n"))
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.
答案1
得分: 0
返回的翻译结果如下:
返回的错误不是一个普通的syscall.Error
,而是包装在*os.PathError
中,如下所示的代码变体:
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// 打开之前创建的命名管道(`mkfifo /tmp/test`)
pipe, _ := os.OpenFile("/tmp/test", os.O_WRONLY, 0)
for {
n, err := pipe.Write([]byte("foo\n"))
fmt.Printf("write: n=%v, err=(%T) %[2]v\n", 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]q\n",
perr.Op, perr.Path, perr.Err)
if perr.Err == syscall.EPIPE {
fmt.Println("os.PathError.Err is EPIPE")
}
}
}
}
在其他地方执行mkfifo /tmp/test; head /tmp/test
后运行此代码,我得到了以下结果:
write: n=4, err=(<nil>) <nil>
[... 重复九次,因为head命令读取了十行 ...]
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
[... 上述三行重复九次 ...]
signal: broken pipe
Exit 1
在对单个文件返回十个管道错误后,Go运行时停止捕获/阻塞SIGPIPE,并将其传递给您的程序以终止它。
我不认为Go运行时允许您捕获或忽略SIGPIPE,因为它在内部使用该信号。
所以有两件事情:一是要查找syscall.EPIPE
,您需要检查*os.PathError
,如上所示;二是,在您实际处理错误之前,当err != nil
时不要继续执行。
我不知道Go为什么以这种方式处理SIGPIPE的详细信息;也许搜索Go的错误跟踪器和/或go-nuts列表可以帮助回答这个问题。
使用Go 1.5对os/signal
包的添加,在任何signal.Notify
调用之前执行signal.Reset(syscall.SIGPIPE)
可以更改此行为。
英文:
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("foo\n"))
fmt.Printf("write: n=%v, err=(%T) %[2]v\n", 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]q\n",
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论