Discussion:
Inaccurate docs for atomically
Andrew Martin
2017-11-12 16:17:40 UTC
Permalink
You cannot use 'atomically' inside an 'unsafePerformIO' or
'unsafeInterleaveIO'. Any attempt to do so will result in a runtime error.
(Reason: allowing this would effectively allow a transaction inside a
transaction, depending on exactly when the thunk is evaluated.)

This doesn't seem to be true. The following program runs fine:

import Control.Monad.STM
import Control.Concurrent.STM.TVar
import System.IO.Unsafe

main :: IO ()
main = do
v <- atomically $ newTVar (7 :: Int)
print $ unsafePerformIO $ atomically $ do
readTVar v

I suspect that the runtime only gives you an error if you actually create a
nested transaction. Is my understanding correct?

-Andrew Thaddeus Martin
Jake McArthur
2017-11-12 19:19:53 UTC
Permalink
I think the point is that by using atomically inside unsafePerformIO you
risk using atomically inside atomically; that is, the bad case is
atomically inside unsafePerformIO inside atomically. The documentation is
wrong in that it only throws an exception when transactions are nested in
this way.
Post by Andrew Martin
import Control.Concurrent.STM
import Data.IORef
import System.IO.Unsafe
main :: IO ()
main = do
ref <- newIORef (6 :: Int)
i <- atomically $ do
var <- newTVar (unsafePerformIO (readIORef ref))
readTVar var
print i
On Sun, Nov 12, 2017 at 11:28 AM, Henning Thielemann <
You cannot use 'atomically' inside an 'unsafePerformIO' or >
'unsafeInterleaveIO'. Any attempt to do so will result in a runtime >
error. (Reason: allowing this would effectively allow a transaction >
inside a transaction, depending on exactly when the thunk is > evaluated.)
I always thought that it would be the other way round, i.e. that you
cannot call 'unsafePerformIO' inside an 'atomically'. Maybe I mixed
something up.
--
-Andrew Thaddeus Martin
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Andrew Martin
2017-11-13 02:44:38 UTC
Permalink
Thanks everyone for feedback on this. I’ve opened a PR at https://github.com/ghc/ghc/pull/87 and any commentary on the haddock changes there is appreciated.

Sent from my iPhone
Post by Andrew Martin
You cannot use 'atomically' inside an 'unsafePerformIO' or 'unsafeInterleaveIO'. Any attempt to do so will result in a runtime error. (Reason: allowing this would effectively allow a transaction inside a transaction, depending on exactly when the thunk is evaluated.)
import Control.Monad.STM
import Control.Concurrent.STM.TVar
import System.IO.Unsafe
main :: IO ()
main = do
v <- atomically $ newTVar (7 :: Int)
print $ unsafePerformIO $ atomically $ do
readTVar v
I suspect that the runtime only gives you an error if you actually create a nested transaction. Is my understanding correct?
-Andrew Thaddeus Martin
Andrew Martin
2017-11-13 02:12:30 UTC
Permalink
Thanks for the examples showcasing how things can go wrong. I had not even
really considered something like the second case. It's weird that STM
discards exception handlers, but the idea of ever trying to smuggle
something like that into an STM transaction seems insane. Interestingly, it
appears that this deadlock can be induced without unsafePerformIO, using
the mildly-less-awful unsafeIOToSTM from GHC.Conc. The documentation for it
backs up your second example.

On Sun, Nov 12, 2017 at 2:41 PM, Bertram Felgenhauer via Libraries <
Post by Andrew Martin
Post by Andrew Martin
You cannot use 'atomically' inside an 'unsafePerformIO' or
'unsafeInterleaveIO'. Any attempt to do so will result in a runtime
error.
Post by Andrew Martin
(Reason: allowing this would effectively allow a transaction inside a
transaction, depending on exactly when the thunk is evaluated.)
import Control.Monad.STM
import Control.Concurrent.STM.TVar
import System.IO.Unsafe
main :: IO ()
main = do
v <- atomically $ newTVar (7 :: Int)
print $ unsafePerformIO $ atomically $ do
readTVar v
I suspect that the runtime only gives you an error if you actually
create a
Post by Andrew Martin
nested transaction. Is my understanding correct?
Yes, that is correct. But you should not conclude from this that using
`unsafePerformIO`, in particular in connection with STM, is safe in any
import Control.Monad.STM
import Control.Concurrent.STM.TVar
import System.IO.Unsafe
import Control.Concurrent.MVar
import Control.Concurrent
import Control.Monad
import System.Mem
-- using STM inside `unsafePerformIO`, used inside `atomically`,
-- causes an error.
--
-- foo: Control.Concurrent.STM.atomically was nested
main1 = do
let val = unsafePerformIO (atomically (return (0 :: Int)))
atomically (return $! val) >>= print
--
-- There is no use of STM in the unsafePerformIO-ed action, but the
-- program ends up taking a resource (an MVar here) without releasing
-- it; it turns out that when retrying an STM action that is in the
-- middle of an unsafePerformIO computation, the IO action is stopped
-- without raising an exception!
--
-- output (tested with ghc 7.10.2, 8.0.2 and 8.2.1, but I see no way
-- foo: thread blocked indefinitely in an MVar operation
main2 = do
var <- newMVar ()
tvar <- atomically $ newTVar (0 :: Int)
let val v = unsafePerformIO $
withMVar var $ \_ -> threadDelay 10000 >> return v
replicateM_ 32 $ do
forkIO $ atomically (readTVar tvar >>= (writeTVar tvar $!) . val
. succ)
threadDelay 100000
performGC
takeMVar var >>= print
main = main2
Cheers,
Bertram
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
--
-Andrew Thaddeus Martin
Loading...