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

Bas van Dijk v.dijk.bas at gmail.com
Sun Dec 9 22:24:09 CET 2012


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



More information about the Libraries mailing list