Trying to understand Haskell STM simple things

1.8k views Asked by At

I got stuck in understanding the concept of atomically in STM.

I illustrate with an example

import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import qualified Data.Map as Map 

main :: IO ()
main =  do
    d <- atomically$ newTVar Map.empty
    sockHandler  d 

sockHandler ::  TVar (Map.Map String Int)-> IO ()
sockHandler  d = do
    forkIO $ commandProcessor  d 1
    forkIO $ commandProcessor  d 2
    forkIO $ commandProcessor  d 3
    forkIO $ commandProcessor  d 4
    forkIO (threadDelay 1000 >> putStrLn "Hello World?")

    threadDelay 10000
    return ()

commandProcessor ::  TVar (Map.Map String Int)-> Int-> IO ()
commandProcessor  d i= do
  addCommand d i
  commandProcessor  d i 

addCommand  ::  TVar (Map.Map String Int) ->Int -> IO ()
addCommand    d i = do
  succ <- atomically $ runAdd d
  putStrLn  $"Result of add in " ++ (show i)++ " " ++( show succ)

runAdd  d =do
  dl <- readTVar d
  let (succ,g)= if   Map.member "a" dl
                  then
                      (False,dl)
                  else
                      (True,Map.insert "a" 9 dl)
  writeTVar d g
  return succ

The sample output would be like this:

Result of add in 1 True Result of add in 4 False Result of add in 1 FalseResult of add in 2 FalseResult of add in 3 False Hello World? Result of add in 4 False

Result of add in 1 FalseResult of add in 2 False Result of add in 3 False Result of add in 4 False

Result of add in 1 False Result of add in 2 FalseResult of add in 3 FalseResult of add in 4 False

Result of add in 1 False Result of add in 2 FalseResult of add in 3 FalseResult of add in 4 False

Result of add in 1 False Result of add in 2 FalseResult of add in 4 FalseResult of add in 3 False

Result of add in 1 False Result of add in 4 FalseResult of add in 2 FalseResult of add in 3 False

Result of add in 1 FalseResult of add in 4 False Result of add in 2 False Result of add in 3 False

Result of add in 1 FalseResult of add in 4 False

Result of add in 2 FalseResult of add in 3 False

Result of add in 1 FalseResult of add in 4 False

Result of add in 2 FalseResult of add in 3 False Result of add in 1 False Result of add in 4 False

Result of add in 2 FalseResult of add in 3 False

Result of add in 1 FalseResult of add in 4 False

When I read about atomically

. This means that all operations inside the transaction fully complete, without any other threads modifying the variables that our transaction is using, or it fails, and the state is rolled back to where it was before the transaction was begun. In short, atomic transactions either complete fully, or it is as if they were never run at all.

So to the question could the "return" of succ in some cases never happen? That is could the line succ <- atomically $ runAdd d putStrLn $"Result of add in " ++ (show i)++ " " ++( show succ)

give an output of "Result of add in ?i " ("as if they were never run at all")

2

There are 2 answers

3
Neil Brown On BEST ANSWER

If a transaction does get rolled back, what happens is that your program tries again. You can imagine the implementation of atomically to be something like this:

atomically action = do varState <- getStateOfTVars
                       (newState, ret) <- runTransactionWith action varState
                       success <- attemptToCommitChangesToTVars newState
                       if success
                         then return ret
                         else atomically action -- try again

In your case, the transaction will always run and will always complete. It may complete on the second or third try due to conflicts, but that's invisible to you, the user. STM makes sure that the action happens atomically, even if it takes a few goes before it's able to do that successfully.

6
Thomas M. DuBuisson On
  1. threadDelay already returns (), no need to explicitly return () afterward
  2. newTVarIO is a concise version of atomically . newTVar.
  3. It's more readable if you use forever instead of tail calling yourself as done in commandProcessor.

As for your question, the answer is "yes". It's called live-lock in which your thread has work to do but it can't make progress. Imagine a really expensive function, expensive, and a really cheap function, cheap. If these operate in competing atomically blocks on the same TVar then the cheap function can cause the expensive function to never complete. I built an example for a related SO question.

Your closing example is not quite right though, if the STM operation never completes then putStrLn will never be reached and no output will be seen at all from that thread.