-- -- (c) Susumu Katayama 2009 -- module MagicHaskeller.TimeOut where import Control.Concurrent(forkIO, killThread, myThreadId, ThreadId, threadDelay, yield) import Control.Concurrent.SampleVar import Control.Exception(catch, Exception(..)) -- import System.Posix.Unistd(getSysVar, SysVar(..)) -- import System.Posix.Process(getProcessTimes, ProcessTimes(..)) -- import System.Posix.Types(ClockTick) import System.CPUTime import System.IO.Unsafe(unsafePerformIO) import Control.Monad(when) import System.IO import Debug.Trace -- This IS necessary to monitor errors in the subprocs. -- import Timeout -- ソースをみた感じ,MVarやSampleVarを作るoverheadは無視できそう. -- data CHTO a = CHTO {timeInMicroSecs :: Int, sv :: SampleVar (Maybe a)} {- unsafeEvaluate :: Int -> a -> Maybe a unsafeEvaluate t e = unsafePerformIO (withTO t (return e)) -- Should I use Control.Exception.evaluate? I do not want to evaluate long lists deeply. -} maybeWithTO :: (a -> IO () -> IO ()) -- ^ seq or deepSeq(=Control.Parallel.Strategies.sforce). For our purposes seq is enough, because @a@ is either 'Bool' or 'Ordering'. -> Maybe Int -> IO a -> IO (Maybe a) maybeWithTO sq mbt action = maybeWithTO' sq mbt (const action) maybeWithTO' :: (a -> IO () -> IO ()) -> Maybe Int -> ((IO b -> IO b) -> IO a) -> IO (Maybe a) maybeWithTO' _ Nothing action = do a <- action id return (Just a) maybeWithTO' dsq (Just t) action = withTO' dsq t action --maybeWithTO' dsq (Just t) action = timeout t (action undefined) -- System.Timeout.timeoutを使うと速くなる. -- 'withTO' creates CHTO every time it is called. Currently unused. -- withTO :: DeepSeq a => Int -> IO a -> IO (Maybe a) -- withTO timeInMicroSecs action = withTO' deepSeq timeInMicroSecs (const action) withTO' :: (a -> IO () -> IO ()) -> Int -> ((IO b -> IO b) -> IO a) -> IO (Maybe a) withTO' dsq timeInMicroSecs action = do -- clk_tck <- getSysVar ClockTick -- let ticks = fromInteger (clk_tck * toInteger timeInMicroSecs `div` 1000000) resSV <- newEmptySampleVar do forkIO (catchIt resSV (do tid <- myThreadId chtid <- forkIO (do threadDelay timeInMicroSecs -- wait deadline -- this line makes sure that timeInMicroSecs has really passed in the process time, but I guess this has no sense, because userTime is shared among Concurrent Haskell threads. writeSampleVar resSV Nothing killThread tid) -- res <- action (catchYield tid resSV) res <- action (yield>>) res `dsq` writeSampleVar resSV (Just res) killThread chtid)) readSampleVar resSV wrapExecution = id --wrapExecution = measureExecutionTime measureExecutionTime act = do begin <- getCPUTime res <- act end <- getCPUTime hPutStrLn stderr (show (end - begin)) return res -- Catch exceptions such as stack space overflow catchIt :: (SampleVar (Maybe a)) -> IO () -> IO () catchIt sv act = Control.Exception.catch act (handler sv) -- catchIt sv act = act -- disable handler sv err = Control.Exception.catch (realHandler sv err) (handler sv) -- realHandler sv (AsyncException ThreadKilled) = return () -- If the thread is killed by ^C (i.e. not by another thread), sv is left empty. So, the parent thread can catch ^C while waiting. #ifdef REALDYNAMIC -- realHandler sv err = trace (show err) $ writeSampleVar sv Nothing realHandler sv (ErrorCall str) = trace str $ writeSampleVar sv Nothing #endif realHandler sv err = writeSampleVar sv Nothing catchYield tid sv action = Control.Exception.catch (yield >> action) (childHandler tid sv) childHandler tid sv err = Control.Exception.catch (realChildHandler tid sv err) (childHandler tid sv) realChildHandler tid sv err = do writeSampleVar sv Nothing killThread tid error "This thread must have been killed...." {- wait deadline = do ticksNow <- getProcessTimes let tickNow = userTime ticksNow when (tickNow < deadline) $ do threadDelay 20000 wait deadline -}