[Haskell-cafe] Race conditions with threadWait(Read/Write) and closeFdWith

John Lato jwlato at gmail.com
Mon Sep 1 23:13:57 UTC 2014


The documentation for threadWaitRead states:

    Block the current thread until data is available to read on the given
file descriptor (GHC only).

    This will throw an IOError if the file descriptor was closed while this
thread was blocked. To safely close a file descriptor that has been used
with threadWaitRead
<http://hackage.haskell.org/package/base-4.7.0.1/docs/GHC-Conc-IO.html#v:threadWaitRead>,
use closeFdWith
<http://hackage.haskell.org/package/base-4.7.0.1/docs/GHC-Conc-IO.html#v:closeFdWith>
.

So what should happen in your situation is that, when a separate thread
closes the fd, threadWaitRead will throw an exception promptly, not
continue to wait on a now-closed fd.

Note the docs for closeFdWith:

    Close a file descriptor in a concurrency-safe way (GHC only). If you
are using threadWaitRead
<http://hackage.haskell.org/package/base-4.7.0.1/docs/GHC-Conc-IO.html#v:threadWaitRead>
 or threadWaitWrite
<http://hackage.haskell.org/package/base-4.7.0.1/docs/GHC-Conc-IO.html#v:threadWaitWrite>
to
perform blocking I/O, you *must* use this function to close file
descriptors, or blocked threads may not be woken.

So I think the short answer is, if you're using fd's and threadWaitRead,
and you always use closeFdWith, you should be protected against operating
on a re-used fd index, even with wrapping your fd's in a Maybe.

I seem to recall that there are some other issues that arise if you attempt
to mix these low-level fd calls with higher-level fd operations, but that
was two IO managers ago so those concerns may be out of date.

John L.


On Mon, Sep 1, 2014 at 4:17 PM, Leon Smith <leon.p.smith at gmail.com> wrote:

> I was very tangentially involved with a use-after-close IO descriptor
> fault recently,  and I came to realize that descriptor indexes are
> typically allocated by the smallest available index,   Previously,  I had
> erroneously believed that the indexes were sequentially allocated by an
> ever-increasing counter,  until wrap-around occurs.
>
> Obviously,  the smallest available index allocation strategy makes
> use-after-close faults rather more significant,  because in a server
> application that is constantly closing and allocating descriptors,  it
> makes it rather more likely that an erroneous operation will actually have
> a tangible effect,  and not simply return an EINVAL or similar errno.
>
> So in my low-level linux-inotify binding,  I felt it wise to add
> protection against use-after-close faults.   The approach I'm currently
> investigating is the obvious one:   to (conceptually) represent the inotify
> socket by a "MVar (Maybe Fd)" value.
>
> However, if there's no file system events to read from the socket,   we
> want to call threadWaitRead,  So somewhere there's some code that's
> essentially this:
>
>      readMVar inotifyfd >>= maybe (throwIO ...) threadWaitRead
>
> So the problem is,  what happens when the thread executing this snippet
> yields in between the readMVar and the threadWaitRead?      It would then
> be possible for another thread (or two) to close the inotify descriptor,
> and then allocate another descriptor with the same index.   The first
> thread would then end up blocking until the new descriptor becomes
> readable,   after which the thread would re-read the MVar and throw an
> exception that the descriptor has been closed.
>
> This is a _relatively_ benign effect,  but still,  it does prevent the
> waiting thread from throwing an exception at the earliest possible moment,
>   and there are several particularly unlucky scenarios I can think of where
> such a thread could be waiting on the wrong descriptor for a very long
> time.  Not to mention that this is a whole family of race conditions,
>  which I haven't really explored the other possible manifestations of which
> might not be as benign.
>
> Somebody did also point me to the new threadWaitReadSTM primitives,  but
> this doesn't cover this use case although the original proposal (properly
> implemented,  of course) would:
>
>
> http://www.haskell.org/ghc/docs/7.8.3/html/libraries/base-4.7.0.1/Control-Concurrent.html
> https://ghc.haskell.org/trac/ghc/ticket/7216
>
>
> In any case,  my linux-inotify binding is available on github;   the
> current main branch is unreleased and also has code to attempt to make
> reading from a descriptor a thread-safe operation.    However I'm not too
> enthusiastic about this unreleased code at the moment, and am considering
> throwing it out.   However,  I'd still very much like to add protection
> against use-after-close faults,  and I'd certainly prefer being able to
> concurrently call both close and read on the descriptor with complete
> safety.
>
> https://github.com/lpsmith/linux-inotify
>
> Best,
> Leon
>
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20140902/2d6b09b5/attachment.html>


More information about the Haskell-Cafe mailing list