英文:
Is it possible to interrupt io.Copy by closing src?
问题
代码示例:
package main
import (
"io"
"os"
"os/signal"
"sync"
"syscall"
)
func main() {
sigintCh := make(chan os.Signal, 1)
signal.Notify(sigintCh, syscall.SIGINT, syscall.SIGTERM)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
io.Copy(os.Stdout, os.Stdin)
}()
<-sigintCh
os.Stdin.Close()
wg.Wait()
}
如果运行这个示例,并尝试通过^C中断,它会等待任何输入,并且只有在向stdin发送一些内容后才停止(例如,只需按回车键)。
我期望关闭Stdin就像发送EOF一样,但它不起作用。
英文:
The sample of code:
package main
import (
"io"
"os"
"os/signal"
"sync"
"syscall"
)
func main() {
sigintCh := make(chan os.Signal, 1)
signal.Notify(sigintCh, syscall.SIGINT, syscall.SIGTERM)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
io.Copy(os.Stdout, os.Stdin)
}()
<-sigintCh
os.Stdin.Close()
wg.Wait()
}
If run this sample and try to interrupt by ^C it waits for any input and stops only after sending something to stdin (e.g. just press enter).
I expect that closing Stdin will be like sending EOF, but it doesn't work.
答案1
得分: 2
关闭os.Stdin会导致io.Copy在下一次从中读取时返回错误file already closed(在按下CTRL-C后,尝试按下Enter键)。
如File.Close文档中所解释的:
> Close关闭文件,使其无法进行I/O操作。
你无法通过关闭os.Stdin(或其他任何方式)来强制从中返回EOF。相反,你需要包装os.Stdin并实现自己的Read方法,有条件地返回EOF,或者在循环中读取有限数量的字节。
你可以在golang-nuts线程中看到更多讨论和可能的解决方法。
英文:
Closing os.Stdin will cause io.Copy to return with error file already closed next time it reads from it (after CTRL-C, try pressing Enter).
As explained in the File.Close docs:
> Close closes the File, rendering it unusable for I/O.
You cannot force an EOF return from os.Stdin by closing it (or any other way). Instead, you would need to either wrap os.Stdin and implement your own Read method that conditionally returns EOF, or read a limited number of bytes in a loop.
You can see some more discussion and possible workarounds on this golang-nuts thread.
答案2
得分: 2
你可以通过使用已经包装了可取消的context.Context的逻辑来中断io.Copy操作,而无需关闭源端。具体的实现可以参考这里的链接。
修改你上面的goroutine如下:
ctx, cancel := context.WithCancel(context.Background())
go func() {
defer wg.Done()
r := NewReader(ctx, os.Stdin) // 包装io.Reader使其具备上下文感知能力
_, err := io.Copy(os.Stdout, r)
if err != nil {
// 如果被中断,会返回context.Canceled错误
}
}()
<-sigintCh
cancel() // 取消上下文将中断io.Copy操作
你可以从外部包导入NewReader,例如github.com/jbenet/go-context/io,或者内联使用上面链接中的代码片段:
type readerCtx struct {
ctx context.Context
r io.Reader
}
func (r *readerCtx) Read(p []byte) (n int, err error) {
if err := r.ctx.Err(); err != nil {
return 0, err
}
return r.r.Read(p)
}
// NewReader 获取一个具备上下文感知能力的io.Reader。
func NewReader(ctx context.Context, r io.Reader) io.Reader {
return &readerCtx{ctx: ctx, r: r}
}
英文:
You can interrupt an io.Copy without closing the source side - by passing an io.Reader that has been wrapped with logic that takes a cancelable context.Context outlined here.
Modify your above goroutine like so:
ctx, cancel := context.WithCancel(context.Background())
go func() {
defer wg.Done()
r := NewReader(ctx, os.Stdin) // wrap io.Reader to make it context-aware
_, err := io.Copy(os.Stdout, r)
if err != nil {
// context.Canceled error if interrupted
}
}()
<-sigintCh
cancel() // canceling context will interrupt io.Copy operation
You can import NewReader from an external package like github.com/jbenet/go-context/io or inline a snippet from the blog link above:
type readerCtx struct {
ctx context.Context
r io.Reader
}
func (r *readerCtx) Read(p []byte) (n int, err error) {
if err := r.ctx.Err(); err != nil {
return 0, err
}
return r.r.Read(p)
}
// NewReader gets a context-aware io.Reader.
func NewReader(ctx context.Context, r io.Reader) io.Reader {
return &readerCtx{ctx: ctx, r: r}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论