-- -- (c) Susumu Katayama -- {-# LANGUAGE CPP, ScopedTypeVariables #-} module MagicHaskeller.TimeOut where import Control.Concurrent(forkIO, killThread, myThreadId, ThreadId, threadDelay, yield) import Control.Concurrent.MVar 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) #if __GLASGOW_HASKELL__ >=702 import System.IO.Unsafe(unsafeDupablePerformIO) #endif import Control.Monad(when) import System.IO import Debug.Trace -- This IS necessary to monitor errors in the subprocs. import qualified System.Timeout import MagicHaskeller.Options(Opt(..)) #ifdef FORCIBLETO import qualified MagicHaskeller.ForcibleTO as ForcibleTO unsafeWithPTOOpt :: ForcibleTO.Byte a => Opt b -> a -> Maybe a unsafeWithPTOOpt opt = let pto = timeout opt in if forcibleTimeout opt then ForcibleTO.unsafeWithPTO pto else unsafeWithPTO pto maybeWithTOOpt opt = let pto = timeout opt in if forcibleTimeout opt then ForcibleTO.maybeWithTO pto else maybeWithTO seq pto #else unsafeWithPTOOpt opt = let pto = timeout opt in unsafeWithPTO pto maybeWithTOOpt opt = let pto = timeout opt in maybeWithTO seq pto #endif unsafeWithPTO :: Maybe Int -> a -> Maybe a #if 0 -- x #if __GLASGOW_HASKELL__ >= 702 unsafeWithPTO pto a = unsafeDupablePerformIO $ wrapExecution ( #else unsafeWithPTO pto a = unsafePerformIO $ wrapExecution ( #endif maybeWithTO seq pto (return $! a) ) 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) newPTO t = return t {- x -- x #else unsafeWithPTO _ = Just maybeWithTO :: c -> b -> IO a -> IO (Maybe a) maybeWithTO _ _ action = do a <- action return (Just a) newPTO = error "not implemented on this platform." -- x #endif -} unsafeOpWithPTO :: Maybe Int -> (a->b->c) -> a -> b -> Maybe c unsafeOpWithPTO mto op l r = unsafeWithPTO mto (op l r) -- ソースをみた感じ,MVarやMSampleVarを作るoverheadは無視できそう. -- data CHTO a = CHTO {timeInMicroSecs :: Int, sv :: MSampleVar (Maybe a)} {- unsafeEvaluate :: Int -> a -> Maybe a unsafeEvaluate t e = unsafePerformIO (withTO t (return e)) -- Should I use Control.OldException.evaluate? I do not want to evaluate long lists deeply. -} 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 = System.Timeout.timeout t (action undefined) -- System.Timeout.timeoutを使うと速くなる. `catch` \(e :: SomeException) -> -- trace ("within maybeWithTO': " ++ show e) $ return Nothing -} maybeWithTO' dsq (Just t) action = do tid <- myThreadId bracket (forkIO (threadDelay t >> -- hPutStrLn stderr "throwing Timeout" >> throwTo tid (ErrorCall "Timeout"))) (\th -> {- block $ -} killThread th) (\_ -> fmap Just $ action (yield>>)) `Control.Exception.catch` \(e :: SomeException) -> -- trace ("within maybeWithTO': " ++ show e) $ return Nothing -- '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) resMV <- newEmptyMVar do (catchIt resMV (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. hPutStrLn stderr "writing Nothing" putMVar resMV Nothing hPutStrLn stderr "killing the main" killThread tid) -- res <- action (catchYield tid resMV) res <- action (yield>>) hPutStrLn stderr "writing Just" res `dsq` putMVar resMV (Just res) hPutStrLn stderr "killing the thread for timeout" killThread chtid)) hPutStrLn stderr "reading MV" readMVar resMV 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 :: (MVar (Maybe a)) -> IO () -> IO () #ifdef REALDYNAMIC catchIt sv act = act -- disable #else catchIt sv act = Control.Exception.catch act (handler sv) #endif handler sv err = Control.Exception.catch (realHandler sv (err::SomeException)) (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) $ putMVar sv Nothing realHandler sv (ErrorCall str) = trace str $ putMVar sv Nothing #endif -} realHandler sv err = putMVar 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::SomeException)) (childHandler tid sv) realChildHandler tid sv err = do putMVar 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 -}