Is it possible to interrupt io.Copy by closing src?

Viewed 2600

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.

2 Answers

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.

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}
}
Related