runST with lazy blackholing breaks referential transparency
A thunk created with runST
can be evaluated twice by different threads producing different results. An example (taken from https://twitter.com/obadzz/status/714081240475951105):
{-# LANGUAGE RecordWildCards #-}
import qualified Data.STRef.Lazy as S
import Control.Monad
import Control.Monad.ST.Lazy
import Control.Concurrent
data ListRef s a = ListRef
{ element :: a
, readCounter :: Int
, rest :: Maybe (S.STRef s (ListRef s a))
}
toList :: S.STRef s (ListRef s a) -> ST s [(a, Int)]
toList r = do
ListRef{..} <- S.readSTRef r
S.modifySTRef r $ \e -> e
{ readCounter = readCounter + 1
}
xs <- maybe (return []) toList rest
return $ (element, readCounter) : xs
circularList :: ST s (S.STRef s (ListRef s Char))
circularList = do
x3 <- S.newSTRef (ListRef 'c' 0 Nothing)
x2 <- S.newSTRef (ListRef 'b' 0 (Just x3))
x1 <- S.newSTRef (ListRef 'b' 0 (Just x2))
S.modifySTRef x3 $ \e -> e
{ rest = Just x1
}
return x1
l :: [(Char, Int)]
l = take 15 $ runST $ circularList >>= toList
main :: IO ()
main = do
void $ forkIO $ print l
void $ forkIO $ print l
void getLine
print l
The output (run multiple times to reproduce):
$ ghc --make -O -threaded -outputdir=.build test.hs
[1 of 1] Compiling Main ( test.hs, .build/Main.o )
Linking test ...
$ ./test +RTS -N2
[('b',0),('b',0),('c',0),('b',1),('b',1),('c',1),('b',2),('b',2),('c',2),('b',3),('b',3),('c',3),('b',5),('b',4),('c',4)]
[('b',0),('b',0),('c',0),('b',1),('b',1),('c',1),('b',2),('b',2),('c',2),('b',3),('b',3),('c',3),('b',4),('b',4),('c',4)]
[('b',0),('b',0),('c',0),('b',1),('b',1),('c',1),('b',2),('b',2),('c',2),('b',3),('b',3),('c',3),('b',4),('b',4),('c',4)]
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.10.1
$
Note that the last 3 elements are ('b',5),('b',4),('c',4)
or ('b',4),('b',4),('c',4)
.
I was able to reproduce it with few weeks old HEAD. With -feager-blackholing
it works as expected.
unsafePerformIO
uses noDuplicate
to prevent such kind of issue. Should runST
do something similar?
Trac metadata
Trac field | Value |
---|---|
Version | 7.10.3 |
Type | Bug |
TypeOfFailure | OtherFailure |
Priority | normal |
Resolution | Unresolved |
Component | Compiler |
Test case | |
Differential revisions | |
BlockedBy | |
Related | |
Blocking | |
CC | |
Operating system | |
Architecture |