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)


> 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,

> Cheers,
> Edward

More information about the Glasgow-haskell-users mailing list