[stm] strange behavior with TQueue and STM's alternative instance
Bas van Dijk
v.dijk.bas at gmail.com
Mon Dec 10 14:24:59 CET 2012
On 10 December 2012 14:12, Simon Marlow <marlowsd at gmail.com> wrote:
> On 10/12/12 12:23, Bas van Dijk wrote:
>>
>> On 10 December 2012 12:51, Simon Marlow <marlowsd at gmail.com> wrote:
>>>
>>> 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
>>>
>>>
>>
>> Great!
>>
>> I would like to understand what went wrong. I looked at the retry and
>> catchRetry PrimOps but didn't understand them well enough to figure
>> out the bug. Do you have a link to your patch?
>
>
> Sure:
> http://hackage.haskell.org/trac/ghc/changeset/f184d9caffa09750ef6a374a7987b9213d6db28e
>
> Cheers,
> Simon
>
Thanks, does this also fix the case when the inner transaction writes
instead of reads? As in:
import Control.Concurrent.STM
main = do
x <- atomically $ do
t <- newTVar 1
writeTVar t 2
((writeTVar t 3 >> retry) `orElse` return ()) `orElse` return ()
readTVar t
print x
Bas
More information about the Libraries
mailing list