Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

theoretical deadlock in Control.Concurrent.Chan readChan

Tags:

haskell

ghc

Browsing the source of readChan one finds the following implementation and comment, starting with version 4.6 of base:

-- |Read the next value from the 'Chan'.
readChan :: Chan a -> IO a
readChan (Chan readVar _) = do
  modifyMVarMasked readVar $ \read_end -> do -- Note [modifyMVarMasked]
    (ChItem val new_read_end) <- readMVar read_end
        -- Use readMVar here, not takeMVar,
        -- else dupChan doesn't work
    return (new_read_end, val)

-- Note [modifyMVarMasked]
-- This prevents a theoretical deadlock if an asynchronous exception
-- happens during the readMVar while the MVar is empty.  In that case
-- the read_end MVar will be left empty, and subsequent readers will
-- deadlock.  Using modifyMVarMasked prevents this.  The deadlock can
-- be reproduced, but only by expanding readMVar and inserting an
-- artificial yield between its takeMVar and putMVar operations.

Prior to base version 4.6, modifyMVar was used rather than modifyMVarMasked.

I don't understand what theoretical problem is solved for here. The last sentence states there is a problem if the thread yields between the takeMVar and putMVar that comprise readMVar. But as readMVar executes under mask_, how can an async exception prevent the put after successful take?

Any help understanding the issue here is appreciated.

like image 678
wrl Avatar asked Apr 21 '26 06:04

wrl


1 Answers

Let's compare the source of modifyMVar and modifyMVarMasked, since the code changed from using one to using the other:

modifyMVar m io =
  mask $ \restore -> do
    a      <- takeMVar m
    (a',b) <- restore (io a) `onException` putMVar m a
    putMVar m a'
    return b

modifyMVarMasked m io =
  mask_ $ do
    a      <- takeMVar m
    (a',b) <- io a `onException` putMVar m a
    putMVar m a'
    return b

The key here is that modifyMVar calls restore before executing its second argument, whereas modifyMVarMasked does not. So readMVar was not called under mask_ in the old version of the code as you claim in your question! It was called under restore, instead, and therefore asynchronous exceptions could be enabled after all.

like image 83
Daniel Wagner Avatar answered Apr 23 '26 10:04

Daniel Wagner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!