Understanding behavior of BlockedIndefinitelyOnMVar exception

Brandon Simmons brandon.m.simmons at gmail.com
Tue Jul 26 23:18:49 CEST 2011


On Tue, Jul 26, 2011 at 1:25 AM, Edward Z. Yang <ezyang at mit.edu> wrote:
> Hello Brandon,
>
> The answer is subtle, and has to do with what references are kept in code,
> which make an object considered reachable.  Essentially, the main thread
> itself keeps the MVar live while it still has forking to do, so that
> it cannot get garbage collected and trigger these errors.

Ah, okay. That seems like an obvious explanation for the exceptions
to be raised at the same time in the forked threads.

>
> Here is a simple demonstrative program:
>
>    main = do
>        lock <- newMVar ()
>        forkIO (takeMVar lock)
>        forkIO (takeMVar lock)
>        forkIO (takeMVar lock)
>

(snip)

>
> But in the meantime (esp. between invocation 2 and 3), the MVar cannot be
> garbage collected, because it is live on the stack.
>
> Could GHC have been more clever in this case?  Not in general, since deciding
> whether or not a reference will actually be used or not boils down to the
> halting problem.
>
>    loop = threadDelay 100 >> loop -- prevent blackholing from discovering this
>    main = do
>        lock <- newEmptyMVar
>        t1 <- newEmptyMVar
>        forkIO (takeMVar lock >> putMVar t1 ())
>        forkIO (loop `finally` putMVar lock ())
>        takeMVar t1
>
> Maybe we could do something where MVar references are known to be writer ends
> or read ends, and let the garbage collector know that an MVar with only read
> ends left is a deadlocked one.  However, this would be a very imprecise
> analysis, and would not help in your original code (since all of your remaining
> threads had the possibility of writing to the MVar: it doesn't become clear
> that they can't until they all hit their takeMVar statements.)

I think this is the crux of what I was confused about. I had assumed
read vs. write was being taken into account by the runtime in raising
BlockedIndefinitelyOnMVar. This makes it obvious:

loop = threadDelay 100 >> loop -- prevent blackholing from discovering this
main = do
   lock <- newEmptyMVar
   forkIO (loop `finally` takeMVar lock)
   takeMVar lock


Given that, I still can't say I understand what is happening in my
original code. I'll try to work out an even simpler example on my own.

Thanks for  the thoughtful response,
Brandon


>
> Cheers,
> Edward
>



More information about the Glasgow-haskell-users mailing list