Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Go, how do I close a long running read?

Tags:

go

goroutine

It doesn't seem possible to have two way communication via channels with a goroutine which is performing file operations, unless you block the channel communication on the file operations. How can I work around the limits this imposes?

Another way to phrase this question...

If I have a loop similar to the following running in a goroutine, how can I tell it to close the connection and exit without blocking on the next Read?

func readLines(response *http.Response, outgoing chan string) error {
    defer response.Body.Close()
    reader := bufio.NewReader(response.Body)

    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            return err
        }
        outgoing <- line
    }
}

It's not possible for it to read from a channel that tells it when to close down because it's blocking on the network reads (in my case, that can take hours).

It doesn't appear to be safe to simply call Close() from outside the goroutine, since the Read/Close methods don't appear to be fully thread safe.

I could simply put a lock around references to response.Body that used inside/outside the routine, but would cause the external code to block until a pending read completes, and I specifically want to be able to interrupt an in-progress read.

like image 511
DonGar Avatar asked Oct 18 '14 07:10

DonGar


People also ask

How do you terminate a Goroutine?

Typically, you pass the goroutine a (possibly separate) signal channel. That signal channel is used to push a value into when you want the goroutine to stop. The goroutine polls that channel regularly. As soon as it detects a signal, it quits.

What is a Goroutine?

A goroutine is a function that executes simultaneously with other goroutines in a program and are lightweight threads managed by Go. A goroutine takes about 2kB of stack space to initialize.

Which builtin function is used for closing a channel CH?

Closing a Channel You can also close a channel with the help of close() function. This is an in-built function and sets a flag which indicates that no more value will send to this channel.


1 Answers

To address this scenario, several io.ReadCloser implementations in the standard library support concurrent calls to Read and Close where Close interrupts an active Read.

The response body reader created by net/http Transport is one of those implementations. It is safe to concurrently call Read and Close on the response body.

You can also interrupt an active Read on the response body by calling the Transport CancelRequest method.

Here's how implement cancel using close on the body:

func readLines(response *http.Response, outgoing chan string, done chan struct{}) error {
    cancel := make(chan struct{})
    go func() {
       select {
       case <-done:
          response.Body.Close()
       case <-cancel:
          return
    }()

    defer response.Body.Close()
    defer close(cancel) // ensure that goroutine exits

    reader := bufio.NewReader(response.Body)
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            return err
        }
        outgoing <- line
    }
}

Calling close(done) from another goroutine will cancel reads on the body.

like image 160
Bayta Darell Avatar answered Sep 18 '22 14:09

Bayta Darell