{-# LANGUAGE FlexibleContexts #-} module Control.Concurrent.Lifted.Instrument ( module Control.Concurrent.Lifted , forkIO , fork , labelThread , threadsInformation , PerThread(..) ) where import qualified Control.Concurrent.Lifted as Raw import Control.Concurrent.Lifted hiding (fork) import Control.Exception (fromException) import Control.Monad.Trans.Control import System.IO.Unsafe import qualified Data.Map.Strict as Map import Control.Exception.Lifted import Control.Monad.Base import qualified GHC.Conc as GHC import Data.Time() import Data.Time.Clock import System.IO import Control.Monad.IO.Class data PerThread = PerThread { lbl :: String , startTime :: UTCTime } deriving (Eq,Ord,Show) data GlobalState = GlobalState { threads :: !(Map.Map ThreadId PerThread) , reportException :: String -> IO () } globals :: MVar GlobalState globals = unsafePerformIO $ newMVar $ GlobalState { threads = Map.empty , reportException = hPutStrLn stderr } {-# NOINLINE globals #-} forkIO :: IO () -> IO ThreadId forkIO = fork {-# INLINE forkIO #-} fork :: (MonadBaseControl IO m, MonadIO m) => m () -> m ThreadId fork action = do t <- Raw.fork $ do tid <- myThreadId tm <- liftBase getCurrentTime bracket_ (modifyThreads $! Map.insert tid (PerThread "" tm)) (return ()) $ do catch action $ \e -> case fromException e of Just ThreadKilled -> return () Nothing -> liftIO $ do g <- takeMVar globals let l = concat [ show e , " (" , maybe "" lbl $ Map.lookup tid (threads g) , ")" ] reportException g l putMVar globals $! g { threads = Map.insert tid (PerThread l tm) $ threads g } throwIO e -- Remove the thread only if it terminated normally or was killed. modifyThreads $! Map.delete tid return t labelThread :: ThreadId -> String -> IO () labelThread tid s = do GHC.labelThread tid s modifyThreads $! Map.adjust (\pt -> pt { lbl = s }) tid {-# INLINE labelThread #-} threadsInformation :: IO [(ThreadId,PerThread)] threadsInformation = do m <- threads <$> readMVar globals return $ Map.toList m modifyThreads :: MonadBase IO m => (Map.Map ThreadId PerThread -> Map.Map ThreadId PerThread) -> m () modifyThreads f = do g <- takeMVar globals let f' st = st { threads = f (threads st) } putMVar globals $! f' g