[stm] strange behavior with TQueue and STM's alternative instance

Simon Marlow marlowsd at gmail.com
Mon Dec 10 12:51:21 CET 2012


On 09/12/12 21:24, Bas van Dijk wrote:
> On 9 December 2012 21:52, Bas van Dijk <v.dijk.bas at gmail.com> wrote:
>> On 9 December 2012 20:50, Bas van Dijk <v.dijk.bas at gmail.com> wrote:
>>> On 9 December 2012 16:48, Patrick Palka <patrick at parcs.ath.cx> wrote:
>>>> Hi,
>>>>
>>>> I'm getting strange behavior when using the 'many' combinator to read zero
>>>> or more items off of a TQueue with readTQueue. The script that exhibits this
>>>> behavior is as follows:
>>>>
>>>> import Control.Concurrent.STM
>>>> import Control.Concurrent
>>>> import Control.Monad
>>>> import Control.Applicative
>>>>
>>>> main = do
>>>>      q <- newTQueueIO
>>>>      atomically $ writeTQueue q True
>>>>      atomically $ writeTQueue q False
>>>>      forever $ do
>>>>          xs <- atomically $ many $ readTQueue q
>>>>          print xs
>>>>          threadDelay 500000
>>>>
>>>>
>>>> I'd expect the output of the script to be:
>>>> [True,False]
>>>> []
>>>> []
>>>> ...
>>>>
>>>> However, that is not the case: the actual output of the script is:
>>>> [True,False]
>>>> [True,False]
>>>> [True,False]
>>>> ...
>>>>
>>>> This means that TQueue is incompatible with TChan, since if TQueue is
>>>> replaced by TChan then the script behaves as one would expect.
>>>>
>>>> If 1 element (say, True) is written into the TQueue instead of 2, then the
>>>> output of the script is:
>>>> [True]
>>>> []
>>>> []
>>>> ...
>>>>
>>>> Which is expected behavior, but inconsistent with the behavior when the
>>>> TQueue has 2 or more elements in it.
>>>>
>>>> Is this considered a bug, or undocumented behavior of TQueue?
>>>>
>>>> _______________________________________________
>>>> Libraries mailing list
>>>> Libraries at haskell.org
>>>> http://www.haskell.org/mailman/listinfo/libraries
>>>>
>>>
>>> This is puzzling me. It looks like a bug in STM. The following code
>>> should have the same behavior. Interestingly, when I remove the
>>> 'readTVar write' marked "Remove me!!!" I get the desired behavior:
>>>
>>> import Control.Concurrent.STM
>>>      (STM, atomically, retry, TVar, newTVarIO, readTVar, writeTVar)
>>> import Control.Concurrent
>>> import Control.Monad
>>> import Control.Applicative
>>>
>>> main = do
>>>    q@(TQueue read write) <- newTQueueIO
>>>    atomically $ writeTQueue q True
>>>    atomically $ writeTQueue q False
>>>    forever $ do
>>>      xs <- atomically $
>>>        (((:) <$> readTQueue q <*>
>>>          (((:) <$> readTQueue q <*>
>>>            (((:) <$> (do readTVar read
>>>                          readTVar write -- Remove me!!!
>>>                          retry
>>>                      ) <*> error "..."
>>>             ) <|> pure []
>>>            )
>>>           ) <|> pure []
>>>          )
>>>         ) <|> pure []
>>>        )
>>>      print xs
>>>      threadDelay 500000
>>>
>>> data TQueue a = TQueue {-# UNPACK #-} !(TVar [a])
>>>                         {-# UNPACK #-} !(TVar [a])
>>>
>>> newTQueueIO :: IO (TQueue a)
>>> newTQueueIO = do
>>>    read  <- newTVarIO []
>>>    write <- newTVarIO []
>>>    return (TQueue read write)
>>>
>>> writeTQueue :: TQueue a -> a -> STM ()
>>> writeTQueue (TQueue _read write) a = do
>>>    listend <- readTVar write
>>>    writeTVar write (a:listend)
>>>
>>> readTQueue :: Show a => TQueue a -> STM a
>>> readTQueue (TQueue read write) = do
>>>    xs <- readTVar read
>>>    case xs of
>>>      (x:xs') -> do writeTVar read xs'
>>>                    return x
>>>      [] -> do ys <- readTVar write
>>>               case ys of
>>>                 [] -> retry
>>>                 _  -> case reverse ys of
>>>                         [] -> error "readTQueue"
>>>                         (z:zs) -> do writeTVar write []
>>>                                      writeTVar read zs
>>>                                      return z
>>>
>>> Bas
>>
>> This is a simplified program which behaves in the same non-expected way:
>>
>> main = do
>>    t <- newTVarIO 1
>>    atomically $ do
>>      writeTVar t 2
>>      ((readTVar t >> retry) `orElse` return ()) `orElse` return ()
>>    atomically (readTVar t) >>= print
>>    threadDelay 500000
>>
>> This program prints 1 while I expect it to print 2.
>>
>> It prints 2 when I remove the outer `orElse`:
>>
>> main = do
>>    t <- newTVarIO 1
>>    atomically $ do
>>      writeTVar t 2
>>      ((readTVar t >> retry) `orElse` return ())
>>    atomically (readTVar t) >>= print
>>    threadDelay 500000
>>
>> It also prints 2 when I remove the readTVar:
>>
>> main = do
>>    t <- newTVarIO 1
>>    atomically $ do
>>      writeTVar t 2
>>      (retry `orElse` return ()) `orElse` return ()
>>    atomically (readTVar t) >>= print
>>    threadDelay 500000
>>
>> Bas
>
> The simplest program I can come up with which shows the same behavior:
>
> $ cat stmTest.hs
> import Control.Concurrent.STM
> main = do
>    x <- atomically $ do
>           t <- newTVar 1
>           writeTVar t 2
>           ((readTVar t >> retry) `orElse` return ()) `orElse` return ()
>           readTVar t
>    print x
>
> $ ghc --make stmTest.hs -fforce-recomp -threaded -o stmTest && ./stmTest
> [1 of 1] Compiling Main             ( stmTest.hs, stmTest.o )
> Linking stmTest ...
> 1

Nice bug!  I have a fix, will try to get it into GHC 7.6.2.

Cheers,
	Simon





More information about the Libraries mailing list