【问题标题】:Getting a pipe status in Go在 Go 中获取管道状态
【发布时间】:2015-11-17 00:47:18
【问题描述】:

我无法在 Go (1.5) 中获得管道状态。

mkfifo 创建的管道上写入时,我尝试获取此输出管道的状态:

  • 使用Write返回状态EPIPE
  • 在 SIGPIPE 上使用 Write 返回状态 EPIPEsignal.Ignore(以防万一)
  • 在 SIGPIPE 上使用 signal.Notify

我可以看到:

  • EPIPE 永不返回
  • 当我使用kill -13 时,调用信号处理程序:“得到信号:损坏的管道”
  • 当我ctrl-c 阅读器时,不会调用信号处理程序,并且我的程序退出并输出:“信号:损坏的管道”

请指出我的错误吗?

// 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")
        }
    }
}

注意:作为一个简单的 Go 练习,我尝试实现一个几乎类似于 tee -a &lt;a_file&gt; 的命令(将 stdin 打印到 stdout 和 &lt;a_file&gt;),具有以下特性:non blocking write on a named pipe and optional reader

【问题讨论】:

    标签: linux go


    【解决方案1】:

    返回的错误不是普通的syscall.Error,而是包含在*os.PathError 中,如以下代码变体所示:

    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")
                }
            }
        }
    }
    

    在其他地方执行mkfifo /tmp/test; head /tmp/test 之后运行它会给我:

    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
    

    在单个文件上返回 10 个管道错误后,Go runtine 停止捕获/阻塞 SIGPIPE 并让它通过你的程序杀死它。 我不相信 Go 运行时允许您捕获或忽略 SIGPIPE,因为它在内部使用该信号本身。

    所以有两件事:第一,要查找syscall.EPIPE,您需要检查*os.PathError,如图所示;第二,当您还没有实际处理错误时,不要继续err != nil

    我不知道为什么 Go 以这种方式处理 SIGPIPE 的细节;也许搜索 Go 的错误跟踪器和/或 go-nuts 列表可能有助于回答这个问题。 随着 Go 1.5 对 os/signal 包的添加,signal.Reset(syscall.SIGPIPE)(在任何 signal.Notify 调用之前)改变了这种行为。

    【讨论】:

    • - 事实上,syscall.EPIPE 是一个 errno 值,而不是“直接”返回状态值。我的错,谢谢。 - “在返回十个管道错误后 [...] 杀死它。”你知道这种行为的原因吗? - 为什么Notify 捕获kill -13 (SIGPIPE) 而不是真正的?
    猜你喜欢
    • 1970-01-01
    • 2018-07-22
    • 1970-01-01
    • 2018-05-12
    • 2012-10-15
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多