🎓 A1Learner
🔍
0 of 47 lessons completed

Haskell STM Retry

Retry restarts a transaction until a box it read changes. It lets a transaction wait for the value it needs.

A worker waits until a flag turns true

A worker retries while the flag is False. The main thread sets it to True, then takes the result.

import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import Control.Concurrent.STM
import Control.Monad (when)

main :: IO ()
main = do
  flag <- newTVarIO False
  box <- newEmptyMVar
  _ <- forkIO $ do
    atomically $ do
      f <- readTVar flag
      when (not f) retry
    putMVar box "ready"
  atomically $ writeTVar flag True
  result <- takeMVar box
  putStrLn result

A worker waits until a count reaches three

A worker retries while the count is below 3. The main thread writes 3, then takes the result.

import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import Control.Concurrent.STM
import Control.Monad (when)

main :: IO ()
main = do
  count <- newTVarIO (0 :: Int)
  box <- newEmptyMVar
  _ <- forkIO $ do
    result <- atomically $ do
      n <- readTVar count
      when (n < 3) retry
      return n
    putMVar box result
  atomically $ writeTVar count 3
  final <- takeMVar box
  print final

A worker waits until a value equals seven

A worker retries while the value is not 7. The main thread writes 7, then takes the result.

import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import Control.Concurrent.STM
import Control.Monad (when)

main :: IO ()
main = do
  value <- newTVarIO (0 :: Int)
  box <- newEmptyMVar
  _ <- forkIO $ do
    result <- atomically $ do
      n <- readTVar value
      when (n /= 7) retry
      return n
    putMVar box result
  atomically $ writeTVar value 7
  final <- takeMVar box
  print final

Example

import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import Control.Concurrent.STM
import Control.Monad (when)

main :: IO ()
main = do
  flag <- newTVarIO False
  count <- newTVarIO (0 :: Int)
  box <- newEmptyMVar
  _ <- forkIO $ do
    result <- atomically $ do
      f <- readTVar flag
      c <- readTVar count
      when (not f || c < 5) retry
      return (f, c)
    putMVar box result
  atomically $ do
    writeTVar flag True
    writeTVar count 5
  final <- takeMVar box
  print final

Key points

  • Retry: retry restarts a transaction until a box it read changes.
  • Waiting transaction: a transaction that calls retry waits instead of finishing with a wrong value.
  • Changed box: retry wakes only when a box the transaction read gets a new value.
  • Main thread change: the main thread changes the watched box, and the waiting transaction then runs again.
  • Rendezvous: the main thread takes the worker result from an MVar after the worker finishes.

In this example, a worker waits for a flag and a count together. The main thread sets the flag to True and the count to 5 in one transaction. The worker then puts the pair into an MVar. The main thread takes it and prints True and 5.

Note

retry belongs inside atomically. It never belongs in ordinary IO code.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Haskell Playground

Exercise

When does a transaction that called retry run again?