FAQ
Working on a project, we came across a need to stop a io.ReadCloser after
we have handed it off to a consumer. The naive solution is to call Close()
on the ReadCloser when we are signaled to cancel. However, this causes a
race when the ReadCloser is a file, as there is no mutex coordinating
access to the file descriptor between the Read and Close operations. I'm
wondering what the best way to do this is, considering I have little
control over the consumer. The CancellableCopy function in the below gist
is the code in question:

https://gist.github.com/tedsuo/8aca75a18d8d7607f598

Any ideas on what a clean, race-free solution would look like?

Thanks,
Ted

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Search Discussions

  • Morozov Alexandr at Dec 9, 2014 at 11:46 pm
    Hello, Ted. We've encountered same problem with closing `os.File`
    datastructure. I tried to wrap file to something like `SafeReadCloser`
    where `Read` and `Close` was protected by mutex, but there was deadlock
    because `Read` sometimes can hang forever without close :( And it seems
    impossible to protect f.fd without outer mutex.
    I will appreciate if you post solution if you find it :)

    вторник, 9 декабря 2014 г., 14:03:38 UTC-8 пользователь tyo...@pivotal.io
    написал:
    Working on a project, we came across a need to stop a io.ReadCloser after
    we have handed it off to a consumer. The naive solution is to call Close()
    on the ReadCloser when we are signaled to cancel. However, this causes a
    race when the ReadCloser is a file, as there is no mutex coordinating
    access to the file descriptor between the Read and Close operations. I'm
    wondering what the best way to do this is, considering I have little
    control over the consumer. The CancellableCopy function in the below gist
    is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598

    Any ideas on what a clean, race-free solution would look like?

    Thanks,
    Ted
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Tyoung at Dec 10, 2014 at 1:27 am
    Yes, we looked at the approach you mentioned, but because you cannot never
    know what is on the other end of an io.ReadCloser, and it's possible for
    Read to block. Also, the middleman ReadCloser would either need to get
    fancy, or get in the way of optimizations that could occur (typechecking
    for ReadFrom, etc).

    In the case of files, I think the close() method could possibly be made
    safer. This line is where the race occurs:

         file.fd = -1 // so it can't be closed again

    http://golang.org/src/pkg/os/file_unix.go?s=2854:2897#L100

    If file.fd was int64 instead of int, then an atomic.StoreInt64(&file.fd,-1)
    I believe would fix the race. If that's a useful approach, I could submit
    a patch.

    On Tuesday, December 9, 2014 3:46:52 PM UTC-8, Morozov Alexandr wrote:

    Hello, Ted. We've encountered same problem with closing `os.File`
    datastructure. I tried to wrap file to something like `SafeReadCloser`
    where `Read` and `Close` was protected by mutex, but there was deadlock
    because `Read` sometimes can hang forever without close :( And it seems
    impossible to protect f.fd without outer mutex.
    I will appreciate if you post solution if you find it :)

    вторник, 9 декабря 2014 г., 14:03:38 UTC-8 пользователь tyo...@pivotal.io
    написал:
    Working on a project, we came across a need to stop a io.ReadCloser after
    we have handed it off to a consumer. The naive solution is to call Close()
    on the ReadCloser when we are signaled to cancel. However, this causes a
    race when the ReadCloser is a file, as there is no mutex coordinating
    access to the file descriptor between the Read and Close operations. I'm
    wondering what the best way to do this is, considering I have little
    control over the consumer. The CancellableCopy function in the below gist
    is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598

    Any ideas on what a clean, race-free solution would look like?

    Thanks,
    Ted
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Morozov Alexandr at Dec 10, 2014 at 1:34 am
    Yes, that would be great. Also you need atomic.LoadInt64 in f.read then.

    вторник, 9 декабря 2014 г., 17:27:19 UTC-8 пользователь tyo...@pivotal.io
    написал:
    Yes, we looked at the approach you mentioned, but because you cannot never
    know what is on the other end of an io.ReadCloser, and it's possible for
    Read to block. Also, the middleman ReadCloser would either need to get
    fancy, or get in the way of optimizations that could occur (typechecking
    for ReadFrom, etc).

    In the case of files, I think the close() method could possibly be made
    safer. This line is where the race occurs:

    file.fd = -1 // so it can't be closed again

    http://golang.org/src/pkg/os/file_unix.go?s=2854:2897#L100

    If file.fd was int64 instead of int, then an
    atomic.StoreInt64(&file.fd,-1) I believe would fix the race. If that's a
    useful approach, I could submit a patch.

    On Tuesday, December 9, 2014 3:46:52 PM UTC-8, Morozov Alexandr wrote:

    Hello, Ted. We've encountered same problem with closing `os.File`
    datastructure. I tried to wrap file to something like `SafeReadCloser`
    where `Read` and `Close` was protected by mutex, but there was deadlock
    because `Read` sometimes can hang forever without close :( And it seems
    impossible to protect f.fd without outer mutex.
    I will appreciate if you post solution if you find it :)

    вторник, 9 декабря 2014 г., 14:03:38 UTC-8 пользователь tyo...@pivotal.io
    написал:
    Working on a project, we came across a need to stop a io.ReadCloser
    after we have handed it off to a consumer. The naive solution is to call
    Close() on the ReadCloser when we are signaled to cancel. However, this
    causes a race when the ReadCloser is a file, as there is no mutex
    coordinating access to the file descriptor between the Read and Close
    operations. I'm wondering what the best way to do this is, considering I
    have little control over the consumer. The CancellableCopy function in the
    below gist is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598

    Any ideas on what a clean, race-free solution would look like?

    Thanks,
    Ted
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Thomas Bushnell, BSG at Dec 10, 2014 at 1:58 am
    That doesn't fix the race. Using atomic doesn't guarantee that the store
    even happens. You can't (in general) fix racy code by using atomic stores.

    Instead, use the sync package (always) whenever you have a race.

    My suggestion to tyoung's original question is to interpose your own
    ReadCloser in between the one you hand off (assuming it's not one you
    haven't already written) and the consumer, and in yours, you can have a
    lock:

    type reader struct {
       r io.ReadCloser
       sync.Mutex
       closed bool
    }

    func (r *reader) Read(buf []byte) (int, error) {
       r.Lock()
       defer r.Unlock()
       if r.closed {
         return whatever you want to happen here [your semantics are not clear]
       }
       return r.r.Read(buf)
    }

    func (r *reader) Close() error {
       r.Lock()
       defer r.Unlock()
       if r.closed {
         return nil // or whatever you want
       }
       return r.r.Close()
    }

    On Tue Dec 09 2014 at 5:34:30 PM Morozov Alexandr wrote:

    Yes, that would be great. Also you need atomic.LoadInt64 in f.read then.

    вторник, 9 декабря 2014 г., 17:27:19 UTC-8 пользователь tyo...@pivotal.io
    написал:
    Yes, we looked at the approach you mentioned, but because you cannot
    never know what is on the other end of an io.ReadCloser, and it's possible
    for Read to block. Also, the middleman ReadCloser would either need to get
    fancy, or get in the way of optimizations that could occur (typechecking
    for ReadFrom, etc).

    In the case of files, I think the close() method could possibly be made
    safer. This line is where the race occurs:

    file.fd = -1 // so it can't be closed again

    http://golang.org/src/pkg/os/file_unix.go?s=2854:2897#L100

    If file.fd was int64 instead of int, then an
    atomic.StoreInt64(&file.fd,-1) I believe would fix the race. If that's a
    useful approach, I could submit a patch.

    On Tuesday, December 9, 2014 3:46:52 PM UTC-8, Morozov Alexandr wrote:

    Hello, Ted. We've encountered same problem with closing `os.File`
    datastructure. I tried to wrap file to something like `SafeReadCloser`
    where `Read` and `Close` was protected by mutex, but there was deadlock
    because `Read` sometimes can hang forever without close :( And it seems
    impossible to protect f.fd without outer mutex.
    I will appreciate if you post solution if you find it :)

    вторник, 9 декабря 2014 г., 14:03:38 UTC-8 пользователь
    tyo...@pivotal.io написал:
    Working on a project, we came across a need to stop a io.ReadCloser
    after we have handed it off to a consumer. The naive solution is to call
    Close() on the ReadCloser when we are signaled to cancel. However, this
    causes a race when the ReadCloser is a file, as there is no mutex
    coordinating access to the file descriptor between the Read and Close
    operations. I'm wondering what the best way to do this is, considering I
    have little control over the consumer. The CancellableCopy function in the
    below gist is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598

    Any ideas on what a clean, race-free solution would look like?

    Thanks,
    Ted
    --
    You received this message because you are subscribed to the Google Groups
    "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an
    email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Andrew Wilkins at Dec 10, 2014 at 2:03 am

    On Wednesday, 10 December 2014 06:03:38 UTC+8, tyo...@pivotal.io wrote:
    Working on a project, we came across a need to stop a io.ReadCloser after
    we have handed it off to a consumer. The naive solution is to call Close()
    on the ReadCloser when we are signaled to cancel. However, this causes a
    race when the ReadCloser is a file, as there is no mutex coordinating
    access to the file descriptor between the Read and Close operations. I'm
    wondering what the best way to do this is, considering I have little
    control over the consumer. The CancellableCopy function in the below gist
    is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598
    <https://www.google.com/url?q=https%3A%2F%2Fgist.github.com%2Ftedsuo%2F8aca75a18d8d7607f598&sa=D&sntz=1&usg=AFQjCNEgMjACBrxrJxcQZnb03axBBhyMNg>

    Any ideas on what a clean, race-free solution would look like?

    Thanks,
    Ted
    One option is to use io.Pipe and a goroutine to copy from the PipeCloser to
    the pipe. io.PipeReader.Read can be cancelled by closing the PipeWriter.

    See: https://github.com/golang/crypto/blob/master/ssh/session.go#L444

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Ted Young at Dec 10, 2014 at 5:39 am
    Ok Thomas, I think I see what you're saying. Because an int is a single
    machine word, go's memory model ensures that there are no partial reads on
    the file descriptor value. So the atomic Load/Store does not change
    anything. Is this correct? I don't care to synchronize a close with any
    particular read, I just want to prevent a read that operates on a partial
    file descriptor. Beyond that, I would like the race detector to shut up,
    but am I correct that the current code is "safe" in this regard?

    My concern with using a mutex (which is how the PipeCloser does it btw) is
    that like Morozov said, reads can potentially block forever. Or,
    ironically, in some cases I think they could block until a close syscall
    unblocks them. So syncing Close and Read can create a deadlock. In my
    code, we want to cancel these operations in situations where timeouts have
    occurred, etc, which may be caused by a stalled read, so I think the
    possibility of a deadlock in production would be high. Given the choice
    between a "race" that is harmless, and a deadlock, I would choose the
    former. I could use a mutex and a timer combined, so that the call to
    close would bail out after a spell, but that's starting to feel ungainly.

    However, I'm sure my thinking on this subject is leaky. I guess my
    question is: what is the best approach to canceling and cleaning up stream
    operations? I would be open to an approach entirely different from the
    CancelableCopy function in my gist, it just felt natural. I assumed
    calling Close would shut down the stream properly.

    Thanks for the feedback,
    Ted
    On Tue, Dec 9, 2014 at 6:02 PM, Andrew Wilkins wrote:
    On Wednesday, 10 December 2014 06:03:38 UTC+8, tyo...@pivotal.io wrote:

    Working on a project, we came across a need to stop a io.ReadCloser after
    we have handed it off to a consumer. The naive solution is to call Close()
    on the ReadCloser when we are signaled to cancel. However, this causes a
    race when the ReadCloser is a file, as there is no mutex coordinating
    access to the file descriptor between the Read and Close operations. I'm
    wondering what the best way to do this is, considering I have little
    control over the consumer. The CancellableCopy function in the below gist
    is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598
    <https://www.google.com/url?q=https%3A%2F%2Fgist.github.com%2Ftedsuo%2F8aca75a18d8d7607f598&sa=D&sntz=1&usg=AFQjCNEgMjACBrxrJxcQZnb03axBBhyMNg>

    Any ideas on what a clean, race-free solution would look like?

    Thanks,
    Ted
    One option is to use io.Pipe and a goroutine to copy from the PipeCloser
    to the pipe. io.PipeReader.Read can be cancelled by closing the PipeWriter.

    See: https://github.com/golang/crypto/blob/master/ssh/session.go#L444
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Manlio Perillo at Dec 10, 2014 at 8:32 am
    Il giorno martedì 9 dicembre 2014 23:03:38 UTC+1, tyo...@pivotal.io ha
    scritto:
    Working on a project, we came across a need to stop a io.ReadCloser after
    we have handed it off to a consumer. The naive solution is to call Close()
    on the ReadCloser when we are signaled to cancel.
    You want to use the operating system async io support.
    As an example POSIX AIO on Freebsd or async IO on Windows.

    Unfortunately async I/O is not supported by Go, AFAIK.
    [...]
    Regards Manlio

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Ian Lance Taylor at Dec 10, 2014 at 3:03 pm

    On Wed, Dec 10, 2014 at 12:32 AM, Manlio Perillo wrote:
    Il giorno martedì 9 dicembre 2014 23:03:38 UTC+1, tyo...@pivotal.io ha
    scritto:
    Working on a project, we came across a need to stop a io.ReadCloser after
    we have handed it off to a consumer. The naive solution is to call Close()
    on the ReadCloser when we are signaled to cancel.

    You want to use the operating system async io support.
    As an example POSIX AIO on Freebsd or async IO on Windows.

    Unfortunately async I/O is not supported by Go, AFAIK.
    Go doesn't support async I/O because it's never necessary with Go.
    And it wouldn't solve this problem either. Using async I/O would
    require checking after each read to see if the file is closed. That
    approach also works in Go.

    For example, have a goroutine that reads from the file and sends the
    data on the channel. Another channel is used to indicate that the
    file should be closed. Each time the goroutine reads a block, it
    checks whether the file should be closed.

    Here is a completely untested example of what I mean:

    http://play.golang.org/p/UO-1yinNKY

    Ian

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • James Bardin at Dec 10, 2014 at 3:41 pm

    On Wed, Dec 10, 2014 at 10:03 AM, Ian Lance Taylor wrote:

    Go doesn't support async I/O because it's never necessary with Go.
    And it wouldn't solve this problem either. Using async I/O would
    require checking after each read to see if the file is closed. That
    approach also works in Go.
    IIRC the behavior of closing an FD during read is undefined if there's an
    error. I don't remember if that comes up in practice, but presumably that
    means the fd may or may not get deallocated?

    A place where lack of async io caught me was that there's no way to
    interrupt a read from a FIFO in Go (Close blocks). Again, not super common,
    but I think it would be fixed by the same means as 6817.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Ian Lance Taylor at Dec 10, 2014 at 6:56 pm

    On Wed, Dec 10, 2014 at 7:41 AM, James Bardin wrote:
    On Wed, Dec 10, 2014 at 10:03 AM, Ian Lance Taylor wrote:

    Go doesn't support async I/O because it's never necessary with Go.
    And it wouldn't solve this problem either. Using async I/O would
    require checking after each read to see if the file is closed. That
    approach also works in Go.

    IIRC the behavior of closing an FD during read is undefined if there's an
    error. I don't remember if that comes up in practice, but presumably that
    means the fd may or may not get deallocated?
    Sorry, I'm not sure exactly what you mean by FD here.

    A place where lack of async io caught me was that there's no way to
    interrupt a read from a FIFO in Go (Close blocks). Again, not super common,
    but I think it would be fixed by the same means as 6817.
    I agree that issue 6817 would be useful. However, I want to
    distinguish between 6817, which essentially says that the Go os
    package should use async I/O where available, from any proposal that
    Go should provide an API for Go programs to use async I/O. The former
    is useful, the latter is not.

    Ian

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • James Bardin at Dec 10, 2014 at 7:09 pm

    On Wed, Dec 10, 2014 at 1:56 PM, Ian Lance Taylor wrote:

    IIRC the behavior of closing an FD during read is undefined if there's an
    error. I don't remember if that comes up in practice, but presumably that
    means the fd may or may not get deallocated?
    Sorry, I'm not sure exactly what you mean by FD here.
    Sorry, referring to the underlying os file descriptor. I can't track down
    the instance I was thinking of that could point to a problem of concurrent
    close and read, but from the POSIX spec:
    If an I/O error occurred while reading from or writing to the file system
    during close(), it may return -1 with errno set to [EIO]; if this error is
    returned, the state of fildes is unspecified

    I feel like I've seen a problem with trying to cancel synchronous reads (at
    least on Linux, possibly with Go), but I can't recall exactly.


    A place where lack of async io caught me was that there's no way to
    interrupt a read from a FIFO in Go (Close blocks). Again, not super common,
    but I think it would be fixed by the same means as 6817.
    I agree that issue 6817 would be useful. However, I want to
    distinguish between 6817, which essentially says that the Go os
    package should use async I/O where available, from any proposal that
    Go should provide an API for Go programs to use async I/O. The former
    is useful, the latter is not.
    Yes, I agree. I'm satisfied with the current api, but would like async I/O
    underneath.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Manlio Perillo at Dec 12, 2014 at 11:24 am
    Il giorno mercoledì 10 dicembre 2014 16:04:05 UTC+1, Ian Lance Taylor ha
    scritto:
    Unfortunately async I/O is not supported by Go, AFAIK.
    Go doesn't support async I/O because it's never necessary with Go.
    It is not necessary but it is desiderable, when async I/O is implemented
    (correctly) in the kernel. The reason is efficiency. For the same reason
    the net package (an only the net package, for reasons I'm not sure to
    fully understand) uses a poller,

    And it wouldn't solve this problem either.


    The problem was "how to interrupt a read/write request".

    Using async I/O would
    require checking after each read to see if the file is closed. That
    approach also works in Go.
    Of course async I/O should be used internally and not exposed to the user.

    For example, have a goroutine that reads from the file and sends the
    data on the channel. Another channel is used to indicate that the
    file should be closed. Each time the goroutine reads a block, it
    checks whether the file should be closed.
    This is fine and idiomatic, however I suspect that it does not scale.
    Suppose, as an example, that you want to use this code in a web
    server handling thousands of connections...
    The other problem is that you usually need an additional buffer copy
    operation, as in your example/

    A kernel supported async I/O can be integrated with the poller used by
    the net package.

    Here is a completely untested example of what I mean:
    Regards Manlio Perillo

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Konstantin Khomoutov at Dec 12, 2014 at 1:39 pm

    On Fri, 12 Dec 2014 03:24:15 -0800 (PST) Manlio Perillo wrote:
    Unfortunately async I/O is not supported by Go, AFAIK.
    Go doesn't support async I/O because it's never necessary with Go.
    It is not necessary but it is desiderable, when async I/O is
    implemented (correctly) in the kernel. The reason is efficiency.
    For the same reason the net package (an only the net package, for
    reasons I'm not sure to fully understand) uses a poller,
    Just a guess, but I'm pretty confident that it's valid at least to some
    degree...

    I have extensively worked with Tcl which had in-core event-driven I/O
    long before all the other popular solutions touting this idea as novel
    came to existence (like Python's Twisted, Node.js etc). The point of
    interest of the Tcl's approach to this is that you are able to set up
    event-driven I/O on any "channel", which, in Tcl's parlance, is
    something on which you can do I/O -- be it a file or an UDP endpoint or
    a TCP socket and basically everything on which you can do read() and/or
    write() down at the kernel level. So you can open a regular file to get
    a channel then set event driven I/O on it and then, say, try to read the
    whole file using "data is ready" callbacks.

    And then in the case of regular files you imminently hit a
    well-observable problem: no matter if you read or write a file, its
    data is "instantly ready", so the matching events basically congest the
    event loop making other users of it (sockets, Tk GUI events -- these
    also use the main Tcl event loop) starve or blocked. In other words,
    the data in normal files are "too ready" to be efficiently operated by
    an event loop which also processes the sources of data with "normal
    readiness", such as network sockets. This effect led to the necessity
    of using clever but ugly hacks which basically pushed the "data ready"
    events originated from file channels to the back of the event loop as
    events of class "idle".

    I think that's basically the reason why Go sort-of assumes the most
    common case: doing the read() or write() syscalls against non-sockets
    will see the instant availability of data and hence the syscalls won't
    block. That could explain why the poller is used for sockets only.

    I don't know how much overhead would async I/O incur. Supposedly not
    so much, so having it in the runtime (and not exposed) would be just the
    right thing -- allowing not to fork OS threads on stalled blocking
    syscalls.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Ian Lance Taylor at Dec 12, 2014 at 2:32 pm

    On Fri, Dec 12, 2014 at 3:24 AM, Manlio Perillo wrote:

    Il giorno mercoledì 10 dicembre 2014 16:04:05 UTC+1, Ian Lance Taylor ha
    scritto:
    Unfortunately async I/O is not supported by Go, AFAIK.
    Go doesn't support async I/O because it's never necessary with Go.
    ..
    Of course async I/O should be used internally and not exposed to the user.
    As I said elsewhere, I agree that the Go runtime should use async I/O
    where possible. In my statement above I meant that it should not be
    part of the user facing API.

    The other problem is that you usually need an additional buffer copy
    operation, as in your example/
    My code was the relatively simple version. The buffer copy is not
    required; omitting it is left as an exercise for the reader.

    Ian

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Nick Patavalis at Dec 10, 2014 at 10:23 am

    On Wednesday, December 10, 2014 12:03:38 AM UTC+2, tyo...@pivotal.io wrote:
    Working on a project, we came across a need to stop a io.ReadCloser after
    we have handed it off to a consumer. The naive solution is to call Close()
    on the ReadCloser when we are signaled to cancel. However, this causes a
    race when the ReadCloser is a file, as there is no mutex coordinating
    access to the file descriptor between the Read and Close operations. I'm
    wondering what the best way to do this is, considering I have little
    control over the consumer. The CancellableCopy function in the below gist
    is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598

    Any ideas on what a clean, race-free solution would look like?
    Calling Close() concurrently with a Read or Write operation does *not*, in
    general, guarantee that the (possibly blocked) Read or Write operation will
    be canceled. If the ReadCloser is a net connection, then it will work
    (close will cancel the operation) reliably. If the ReadCloser is an os.File
    it depends on the operating system and possibly the type of file (regular
    "disk" file, device node, etc). It might work or it might not. If the
    ReadCloser is something else entirely, then it depends on the
    implementation. There is nothing in the ReadCloser interface specification
    that guarantees the cancel-able behavior you seek, in the general case.

    So, I guess, the answer to your question depends on what you are trying to
    do, specifically. If the question is general, that is: How do I cancel an
    ongoing Read of ANY ReadCloser, then the answer is: You can't (not
    generally).

    If the ReadCloser is an os.File, then again, the answer is *You can't*: It
    depends on the OS and the type of the file whether such an operation in
    meaningful but, in any case, the current implementation of os.File does not
    support this.

    I don't know what you are trying to do, but this might (or might not) be of
    some use to you (it works only for Linux):

    https://github.com/npat-efault/poller

    /npat

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • James Bardin at Dec 10, 2014 at 2:34 pm
    For anyone who want to follow it, the relevant issue is
    golang.org/issue/6817 (adding async file IO)

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Nick Craig-Wood at Dec 10, 2014 at 3:34 pm

    On 09/12/14 22:03, tyoung@pivotal.io wrote:
    Working on a project, we came across a need to stop a io.ReadCloser
    after we have handed it off to a consumer. The naive solution is to
    call Close() on the ReadCloser when we are signaled to cancel. However,
    this causes a race when the ReadCloser is a file, as there is no mutex
    coordinating access to the file descriptor between the Read and Close
    operations. I'm wondering what the best way to do this is, considering
    I have little control over the consumer. The CancellableCopy function
    in the below gist is the code in question:

    https://gist.github.com/tedsuo/8aca75a18d8d7607f598

    Any ideas on what a clean, race-free solution would look like?
    Here is an alternative idea.

    You could type cast the io.ReadCloser to a *os.File and if that
    succeeded you could use the Fd() method to get the underlying file
    descriptor and close that with syscall.Close() instead of calling the
    ReadCloser.Close() method.

    This will make all uses of the file descriptor return an error and will
    use the OSes locking on the file descriptor which should cope fine with
    concurrent read and close.

    --
    Nick Craig-Wood <nick@craig-wood.com> -- http://www.craig-wood.com/nick

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Jesper Louis Andersen at Dec 10, 2014 at 4:01 pm

    On Tue, Dec 9, 2014 at 11:03 PM, wrote:

    Any ideas on what a clean, race-free solution would look like?

    I'd probably just go with a solution where the thirdPartyconsumer has full
    jurisdiction over the ReadCloser; and where the interace is message
    passing. So it will periodically check if it needs to close by reading on a
    channel and break the copy into chunks. It then exits early if a close
    happens while the copy is being done. Also, this allows the 3rd party to
    carry out cleanup in the event where something such needs to happen. There
    is no race in this solution, as there are only messages and no shared data,
    and data are only being manipulated by one goroutine. Furthermore, a
    close-message only happens in an invariant-safe way, where no chunk is
    being copied by the 3rd party process. In a generalized scheme, you would
    use something like the context package to signal the close/cancel I guess.

    Yes, it is sad you can't use io.Copy, but such is life in the world of
    proper concurrency :)


    --
    J.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Tyoung at Dec 10, 2014 at 5:48 pm
    It seems like the ReadCloser contract generally expects that Read and Close
    are called synchronously, so it's not a good idea to split these operations
    into two separate goroutines. I'll focus on a synchronous, channel-based
    solution for my problem. If I have any special insight, I'll post it here.

    On Wednesday, December 10, 2014 8:01:41 AM UTC-8, Jesper Louis Andersen
    wrote:
    On Tue, Dec 9, 2014 at 11:03 PM, <tyo...@pivotal.io <javascript:>> wrote:

    Any ideas on what a clean, race-free solution would look like?

    I'd probably just go with a solution where the thirdPartyconsumer has full
    jurisdiction over the ReadCloser; and where the interace is message
    passing. So it will periodically check if it needs to close by reading on a
    channel and break the copy into chunks. It then exits early if a close
    happens while the copy is being done. Also, this allows the 3rd party to
    carry out cleanup in the event where something such needs to happen. There
    is no race in this solution, as there are only messages and no shared data,
    and data are only being manipulated by one goroutine. Furthermore, a
    close-message only happens in an invariant-safe way, where no chunk is
    being copied by the 3rd party process. In a generalized scheme, you would
    use something like the context package to signal the close/cancel I guess.

    Yes, it is sad you can't use io.Copy, but such is life in the world of
    proper concurrency :)


    --
    J.
    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Jesper Louis Andersen at Dec 12, 2014 at 8:42 pm

    On Wed, Dec 10, 2014 at 6:48 PM, wrote:
    It seems like the ReadCloser contract generally expects that Read and
    Close are called synchronously, so it's not a good idea to split these
    operations into two separate goroutines. I'll focus on a synchronous,
    channel-based solution for my problem. If I have any special insight, I'll
    post it here.

    My point here was merely that you can't split Read and Close over multiple
    goroutines. Instead, you have to pass the Close request on to the some
    goroutine which is doing the Read, i.e., the copier calling io.Copy. But
    this means you have to make sure your goroutine checks for close-calls
    regularly and this means you have to chunk the copy-operation. It is a
    quite common thing to split up operations into small batches to achieve
    better latency at the expense of some minuscule performance.


    --
    J.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • James Bardin at Dec 12, 2014 at 8:51 pm

    On Fri, Dec 12, 2014 at 3:41 PM, Jesper Louis Andersen wrote:
    My point here was merely that you can't split Read and Close over multiple
    goroutines. Instead, you have to pass the Close request on to the some
    goroutine which is doing the Read, i.e., the copier calling io.Copy. But
    this means you have to make sure your goroutine checks for close-calls
    regularly and this means you have to chunk the copy-operation. It is a
    quite common thing to split up operations into small batches to achieve
    better latency at the expense of some minuscule performance.


    The problem you run into in this case, is that without async io Read may
    block indefinitely.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • Jesper Louis Andersen at Dec 12, 2014 at 8:58 pm

    On Fri, Dec 12, 2014 at 9:51 PM, James Bardin wrote:
    The problem you run into in this case, is that without async io Read may
    block indefinitely.
    Yes, I've been there with an unresponsive NFS server back in 2001-2002.
    With a better async io support in the runtime, you may be able to support a
    DeadlineRead which also takes a timeout and can progress if the read cannot
    complete in due time. But this is definitely a problem for the future.
    Async IO does not seem to be standardized much over different operating
    systems and it seems outright impossible to get the DeadlineRead semantics
    in a POSIX environment where I/O never "blocks" because there are always
    "bytes available" and then, when you try reading, you learn it has to go,
    switch tapes in the tape robot, read data into a disk cache and then serve
    you the data. Only takes 5 mins, but data are there :)


    --
    J.

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.
  • James Bardin at Dec 12, 2014 at 9:29 pm

    On Fri, Dec 12, 2014 at 3:58 PM, Jesper Louis Andersen wrote:
    On Fri, Dec 12, 2014 at 9:51 PM, James Bardin wrote:

    The problem you run into in this case, is that without async io Read may
    block indefinitely.
    Yes, I've been there with an unresponsive NFS server back in 2001-2002.
    With a better async io support in the runtime, you may be able to support a
    DeadlineRead which also takes a timeout and can progress if the read cannot
    complete in due time. But this is definitely a problem for the future.
    Async IO does not seem to be standardized much over different operating
    systems and it seems outright impossible to get the DeadlineRead semantics
    in a POSIX environment where I/O never "blocks" because there are always
    "bytes available" and then, when you try reading, you learn it has to go,
    switch tapes in the tape robot, read data into a disk cache and then serve
    you the data. Only takes 5 mins, but data are there :)


    It's not just on broken or non-responsive systems though. Reading from a
    FIFO, or even just stdin can block forever. If the underlying io has a
    cross platform poller like the net package, then calling Close from another
    goroutine can be synchronized by the runtime, and users don't need to worry
    about synchronizing a Close call in the middle of io (just like we do now
    with the net package).

    --
    You received this message because you are subscribed to the Google Groups "golang-nuts" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupgolang-nuts @
categoriesgo
postedDec 9, '14 at 11:03p
activeDec 12, '14 at 9:29p
posts24
users11
websitegolang.org

People

Translate

site design / logo © 2021 Grokbase