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

Bas van Dijk v.dijk.bas at gmail.com
Sun Dec 9 21:52:12 CET 2012


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



More information about the Libraries mailing list