Search code examples
haskellstm

Creating a unique time based ID generator with TMVar


I am making a naive time based unique ID generator to learn some concepts in concurrent Haskell.

next should return a unique sequential POSIX time but this timestamp does not have to match the exact moment that I called next.

This code obviously does not generate unique timestamps, and I don't understand why:

import Control.Concurrent.Async (mapConcurrently)
import Data.Time.Clock.POSIX    (getPOSIXTime)
import Data.Time                (UTCTime)
import Control.Concurrent.STM   (TMVar, atomically, takeTMVar, putTMVar, newTMVarIO)
import Data.List                (nub)

getTime :: (Integral b, RealFrac a) => a -> IO b
getTime precision =
  round . (* precision) . fromRational . toRational <$> getPOSIXTime

next :: Integral b => TMVar b -> IO b
next s = do
  t <- getTime 1 -- one second precision
  atomically $ do
    v <- takeTMVar s
    let t' = if t == v then t + 1 else t
    putTMVar s t'
    return t'

main = do
  next' <- next <$> newTMVarIO 0
  res <- mapConcurrently id [next' | _ <- [1 .. 100]]
  print $ length $ nub res -- expected 100

Solution

  • Suppose we call next a few times within the same second and getTime 1 returns 42 each time.

    The first time, t is 42, v is 0, t /= v so t' is 42.

    The second time, t is 42, v is 42, t == v so t' is 43, we store 43.

    The third time, t is 42, v is 43, t /= v so t' is 42, we store 42.

    The fourth time, t is 42, v is 42, t == v so t' is 43, we store 43.

    See the problem?