On Tue, Nov 29, 2005 at 12:29:55PM -0000, Simon Marlow wrote: > > > Alternatively, it would be nice to have a new STM primitive: > > > > wailUntil :: ClockTime -> STM () > > > > so you would wait until some time-point passes, not for a number of > > time-units (waiting for a number of time-units wouldn't work because > > of retries). I think it could be efficiently implemented, wouldn't it? > > But you could also implement this using registerTimeout, albeit with > some more code and an extra thread, and waitUntil requires an > implementation in the runtime which is not entirely trivial.
It is trivial to create a very inefficient implementation, in which all STM transactions waiting on waitUntil will be retried on (almost) every tick of the clock, let's say every second. You just create a TVar that is updated with current time every second. But as I say, the efficiency would be unacceptable. But this can be improved. I found a simple solution that reduces the number of transaction retries to O(log (t - t0)), where t0 is transaction start time, and t is the parameter to waitUntil. I attached a proof-of-concept implementation to this message. I simply use the binary representation of time and wait only on the part of bits, starting from most significant ones, that are enough to tell that the waitUntil time has not come. To make it simple I used unix epoch time in seconds, but the thing could be made more precise. I the thread updating the time variable I make sure that I don't update the bits that didn't change. You can compile the Test module as a program. There are two kinds of tests, some that show how many tries are made, and one that shows how the thing works for many threads. BTW, I tried to make the library interface simpler creating a default top-level time variable {-# NOINLINE timeVar #-} timeVar :: TimeVar timeVar = unsafePerformIO initTimeVar so I could export a waitUntil function with type waitUntil :: Time -> STM () but I tripped on something that was reported before, namely that STM transactions can't be nested (as a result of unsafePerformIO or unsafeInterleaveIO). Is there a plan to support such scenario? Best regards Tomasz -- I am searching for a programmer who is good at least in some of [Haskell, ML, C++, Linux, FreeBSD, math] for work in Warsaw, Poland
module Main where import Debug.Trace import Control.Concurrent.STM import Control.Concurrent import Control.Monad import TimeVar import Random import Control.Exception (finally) main = do runTests [ test 1 , testMany , test 5 , test 10 ] runTests fs = do waitUntil <- liftM waitOnTimeVar initTimeVar mapM_ (\t -> t waitUntil >> putStrLn "") fs -- waits for a given number of seconds -- the STM transaction has a trace instruction that prints "try" -- on every (re)try test secs waitUntil = do putStrLn ("Testing waiting for " ++ show secs ++ " seconds") time1 <- getTime atomically $ do stmTrace "try" waitUntil (time1 + secs) time2 <- getTime putStrLn $ concat $ [ show time2 , " - " , show time1 , " = " , show (time2 - time1) ] -- spawn many threads, each of which -- - takes the current time -- - waits for a random number of seconds -- - takes the current time again and checks that it slept for the -- correct number of seconds testMany waitUntil = do putStrLn "Testing many threads" messages <- atomically newTChan threadCount <- atomically (newTVar 0) let forkIO' t = do atomically (modifyTVar threadCount succ) forkIO (t `finally` atomically (modifyTVar threadCount pred)) replicateM 100 $ forkIO' $ do secs <- liftM fromIntegral (randomRIO (1 :: Int, 10)) time1 <- getTime atomically $ do waitUntil (time1 + secs) time2 <- getTime atomically $ writeTChan messages $ concat $ [ "waited for " , show (time2 - time1) , " seconds, difference from requested: " , show ((time2 - time1) - secs) ] let loop = do join $ atomically $ (do msg <- readTChan messages return (putStrLn msg >> loop)) `orElse` (do n <- readTVar threadCount guard (n == 0) return (return ())) loop -------------------------------------------------------------------------------- stmTrace s = do v <- newTVar () trace s v `seq` return () modifyTVar v f = readTVar v >>= writeTVar v . f
module TimeVar ( TimeVar , Time , getTime , initTimeVar , waitOnTimeVar ) where import Control.Concurrent.STM import Control.Concurrent import Control.Monad import System.Time (getClockTime, ClockTime(..)) import Data.Bits import Data.Int newtype TimeVar = TimeVar [TVar Bool] type Time = Int32 getTime :: IO Time getTime = do TOD secs _ <- getClockTime return (fromIntegral secs) initTimeVar :: IO TimeVar initTimeVar = do t0 <- liftM toBitsFromMSB getTime -- create the "bit vars" vars <- atomically $ do sequence [ do v <- newTVar b return v | b <- t0 ] -- fork a bit vars update thread forkIO $ do sequence_ $ repeat $ do -- update every 0.1 sec threadDelay 100000 t <- liftM toBitsFromMSB getTime atomically $ do sequence_ [ do old <- readTVar var -- don't update the bits that haven't changed when (old /= new) (writeTVar var new) | (var, new) <- zip vars t ] return (TimeVar vars) waitOnTimeVar :: TimeVar -> Time -> STM () waitOnTimeVar (TimeVar vars) t = do let tBits = toBitsFromMSB t cmp vars tBits where cmp (v:vs) (b:bs) = do vVal <- readTVar v case compare vVal b of LT -> retry EQ -> cmp vs bs GT -> return () cmp [] [] = return () toBitsFromMSB :: Bits b => b -> [Bool] toBitsFromMSB x = [ testBit x i | i <- [nBits-1, nBits-2 .. 0] ] where nBits = bitSize x
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe