1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
{-# LANGUAGE FlexibleContexts #-}
module Control.Concurrent.Lifted.Instrument
( module Control.Concurrent.Lifted
, forkLabeled
, forkIO
, forkOS
, forkOSLabeled
, fork
, labelThread
, threadsInformation
, PerThread(..)
) where
import qualified Control.Concurrent.Lifted as Raw
import Control.Concurrent.Lifted hiding (fork,forkOS)
import Control.Exception (fromException)
import Control.Monad.Trans.Control
import GHC.Stack
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 DPut
import DebugTag
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 = dput XMisc
}
{-# NOINLINE globals #-}
forkLabeled :: HasCallStack => String -> IO () -> IO ThreadId
forkLabeled lbl action = do
t <- instrumented GHC.forkIO action
labelThread t lbl
return t
{-# INLINE forkLabeled #-}
forkOSLabeled :: HasCallStack => String -> IO () -> IO ThreadId
forkOSLabeled lbl action = do
t <- instrumented Raw.forkOS action
labelThread t lbl
return t
{-# INLINE forkOSLabeled #-}
forkIO :: HasCallStack => IO () -> IO ThreadId
forkIO = instrumented GHC.forkIO
{-# INLINE forkIO #-}
forkOS :: ( HasCallStack, MonadBaseControl IO m ) => m () -> m ThreadId
forkOS = instrumented Raw.forkOS
{-# INLINE forkOS #-}
fork :: ( HasCallStack, MonadBaseControl IO m ) => m () -> m ThreadId
fork = instrumented Raw.fork
{-# INLINE fork #-}
shortCallStack :: [([Char], SrcLoc)] -> String
shortCallStack [] = ""
shortCallStack ((_,loc):_) = (srcLocFile loc) ++ ":" ++ show (srcLocStartLine loc)
defaultLabel :: CallStack -> String
defaultLabel stack = case getCallStack stack of
_ : sites -> shortCallStack sites
sites -> shortCallStack sites
instrumented :: ( HasCallStack, MonadBaseControl IO m ) =>
(m () -> m ThreadId) -> m () -> m ThreadId
instrumented rawFork action = do
mvar <- newEmptyMVar
t <- rawFork $ do
tid <- myThreadId
tm <- liftBase getCurrentTime
bracket_ (modifyThreads $! \ts -> Map.union ts (Map.singleton tid (PerThread (defaultLabel callStack) tm)))
(return ())
$ do catch action $ \e -> case fromException e of
Just ThreadKilled -> return ()
Nothing -> liftBase $ do
bracket (takeMVar globals)
(\g -> do
let l = concat [ show e
, " ("
, maybe "" lbl $ Map.lookup tid (threads g)
, ")"
]
foldr seq (return ()) l
putMVar globals $! g { threads = Map.insert tid (PerThread l tm) $ threads g }
throwIO e)
(\g -> do
let l = concat [ show e
, " ("
, maybe "" lbl $ Map.lookup tid (threads g)
, ")"
]
reportException g l)
-- Remove the thread only if it terminated normally or was killed.
takeMVar mvar
modifyThreads $! Map.delete tid
liftBase $ labelThread_ t (defaultLabel callStack)
putMVar mvar ()
return t
labelThread_ :: ThreadId -> String -> IO ()
labelThread_ tid s = do
foldr seq (return ()) s
GHC.labelThread tid s
tm <- liftBase getCurrentTime
let updateIt (Just pt) = Just $ pt { lbl = s }
updateIt Nothing = Just $ PerThread s tm
modifyThreads $! Map.alter updateIt tid
labelThread :: ThreadId -> String -> IO ()
labelThread tid s = do
foldr seq (return ()) s
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 :: MonadBaseControl IO m =>
(Map.Map ThreadId PerThread -> Map.Map ThreadId PerThread) -> m ()
modifyThreads f = do
let f' st = st { threads = f (threads st) }
bracket (takeMVar globals)
(\g -> putMVar globals $! f' g)
(\g -> return ())
|