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

Leon Smith leon.p.smith at gmail.com
Mon Sep 1 08:17:54 UTC 2014


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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20140901/298d04e1/attachment.html>


More information about the Haskell-Cafe mailing list