在Go语言中获取管道状态

huangapple go评论89阅读模式
英文:

Getting a pipe status in Go

问题

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

在写入一个mkfifo创建的管道时,我尝试获取该输出管道的状态:

  • 使用Write返回状态EPIPE
  • 使用Write返回状态EPIPEsignal.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 status EPIPE
  • using the Write return status EPIPE and signal.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 (
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;os/signal&quot;
    &quot;syscall&quot;

    sys &quot;golang.org/x/sys/unix&quot;
)

// wait for a signal and print it
func handleSignal(csig chan os.Signal) {
    for {
        fmt.Println(&quot;Wait signal&quot;)
        s := &lt;-csig
        fmt.Println(&quot;Got signal:&quot;, s)
    }
}

func main() {
    csig := make(chan os.Signal, 1)

    // `kill -13` outputs&#160;&quot;Got signal: broken pipe&quot; =&gt; 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(&quot;/tmp/test&quot;, os.O_WRONLY, 0)

    for {
        _, err := pipe.Write([]byte(&quot;foo\n&quot;))
        if err == syscall.EPIPE {
            // never called =&gt; ko
            fmt.Println(&quot;EPIPE error&quot;)
        }
    }
}

Note: as a simple Go exercise, I try to implement a command which almost acts like tee -a &lt;a_file&gt; (print stdin to stdout and &lt;a_file&gt;) 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 (
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;syscall&quot;
)

func main() {
	// open previously created named pipe (`mkfifo /tmp/test`)
	pipe, _ := os.OpenFile(&quot;/tmp/test&quot;, os.O_WRONLY, 0)
	for {
		n, err := pipe.Write([]byte(&quot;foo\n&quot;))
		fmt.Printf(&quot;write: n=%v, err=(%T) %[2]v\n&quot;, n, err)
		if err == syscall.EPIPE {
			fmt.Println(&quot;EPIPE error&quot;)
		} else if perr, ok := err.(*os.PathError); ok {
			fmt.Printf(&quot;op: %q; path=%q; err=(%T) %[3]q\n&quot;,
				perr.Op, perr.Path, perr.Err)
			if perr.Err == syscall.EPIPE {
				fmt.Println(&quot;os.PathError.Err is EPIPE&quot;)
			}
		}
	}
}

Running this after doing mkfifo /tmp/test; head /tmp/test elsewhere gives me:

write: n=4, err=(&lt;nil&gt;) &lt;nil&gt;
[… repeated nine more times, as the head command reads ten lines …]
write: n=0, err=(*os.PathError) write /tmp/test: broken pipe
op: &quot;write&quot;; path=&quot;/tmp/test&quot;; err=(syscall.Errno) &quot;broken pipe&quot;
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.

huangapple
  • 本文由 发表于 2015年8月23日 22:55:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/32168006.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定