diff options
author | Joe Crayne <joe@jerkface.net> | 2020-01-13 06:58:48 -0500 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2020-01-14 03:37:41 -0500 |
commit | cb28281a2acabf87e91582ce5ace562544ae2730 (patch) | |
tree | 4c038981711cf2d1625c4c5766b7f8eab21de0b9 | |
parent | b56ac5aa4a4d9c026f6aadad58daeee7729e9f4c (diff) |
Fixed race condition in thread instrumentation.
-rw-r--r-- | lifted-concurrent/src/Control/Concurrent/Lifted/Instrument.hs | 75 |
1 files changed, 37 insertions, 38 deletions
diff --git a/lifted-concurrent/src/Control/Concurrent/Lifted/Instrument.hs b/lifted-concurrent/src/Control/Concurrent/Lifted/Instrument.hs index a0bb7dc5..bd6ee4b8 100644 --- a/lifted-concurrent/src/Control/Concurrent/Lifted/Instrument.hs +++ b/lifted-concurrent/src/Control/Concurrent/Lifted/Instrument.hs | |||
@@ -1,4 +1,5 @@ | |||
1 | {-# LANGUAGE FlexibleContexts #-} | 1 | {-# LANGUAGE FlexibleContexts #-} |
2 | {-# LANGUAGE RankNTypes #-} | ||
2 | module Control.Concurrent.Lifted.Instrument | 3 | module Control.Concurrent.Lifted.Instrument |
3 | ( module Control.Concurrent.Lifted | 4 | ( module Control.Concurrent.Lifted |
4 | , forkLabeled | 5 | , forkLabeled |
@@ -11,6 +12,7 @@ module Control.Concurrent.Lifted.Instrument | |||
11 | , PerThread(..) | 12 | , PerThread(..) |
12 | ) where | 13 | ) where |
13 | 14 | ||
15 | import Control.Concurrent as Raw (forkOSWithUnmask) | ||
14 | import qualified Control.Concurrent.Lifted as Raw | 16 | import qualified Control.Concurrent.Lifted as Raw |
15 | import Control.Concurrent.Lifted hiding (fork,forkOS) | 17 | import Control.Concurrent.Lifted hiding (fork,forkOS) |
16 | import Control.Exception (fromException) | 18 | import Control.Exception (fromException) |
@@ -47,28 +49,29 @@ globals = unsafePerformIO $ newMVar $ GlobalState | |||
47 | 49 | ||
48 | forkLabeled :: HasCallStack => String -> IO () -> IO ThreadId | 50 | forkLabeled :: HasCallStack => String -> IO () -> IO ThreadId |
49 | forkLabeled lbl action = do | 51 | forkLabeled lbl action = do |
50 | t <- instrumented GHC.forkIO action | 52 | t <- instrumented GHC.forkIOWithUnmask action |
51 | labelThread t lbl | 53 | labelThread t lbl |
52 | return t | 54 | return t |
53 | {-# INLINE forkLabeled #-} | 55 | {-# INLINE forkLabeled #-} |
54 | 56 | ||
55 | forkOSLabeled :: HasCallStack => String -> IO () -> IO ThreadId | 57 | forkOSLabeled :: HasCallStack => String -> IO () -> IO ThreadId |
56 | forkOSLabeled lbl action = do | 58 | forkOSLabeled lbl action = do |
57 | t <- instrumented Raw.forkOS action | 59 | t <- instrumented Raw.forkOSWithUnmask action |
58 | labelThread t lbl | 60 | labelThread t lbl |
59 | return t | 61 | return t |
60 | {-# INLINE forkOSLabeled #-} | 62 | {-# INLINE forkOSLabeled #-} |
61 | 63 | ||
62 | forkIO :: HasCallStack => IO () -> IO ThreadId | 64 | forkIO :: HasCallStack => IO () -> IO ThreadId |
63 | forkIO = instrumented GHC.forkIO | 65 | forkIO = instrumented GHC.forkIOWithUnmask |
64 | {-# INLINE forkIO #-} | 66 | {-# INLINE forkIO #-} |
65 | 67 | ||
66 | forkOS :: ( HasCallStack, MonadBaseControl IO m ) => m () -> m ThreadId | 68 | forkOS :: -- ( HasCallStack, MonadBaseControl IO m ) => m () -> m ThreadId |
67 | forkOS = instrumented Raw.forkOS | 69 | HasCallStack => IO () -> IO ThreadId |
70 | forkOS = instrumented Raw.forkOSWithUnmask | ||
68 | {-# INLINE forkOS #-} | 71 | {-# INLINE forkOS #-} |
69 | 72 | ||
70 | fork :: ( HasCallStack, MonadBaseControl IO m ) => m () -> m ThreadId | 73 | fork :: ( HasCallStack, MonadBaseControl IO m ) => m () -> m ThreadId |
71 | fork = instrumented Raw.fork | 74 | fork = instrumented Raw.forkWithUnmask |
72 | {-# INLINE fork #-} | 75 | {-# INLINE fork #-} |
73 | 76 | ||
74 | shortCallStack :: [([Char], SrcLoc)] -> String | 77 | shortCallStack :: [([Char], SrcLoc)] -> String |
@@ -80,47 +83,43 @@ defaultLabel stack = case getCallStack stack of | |||
80 | _ : sites -> shortCallStack sites | 83 | _ : sites -> shortCallStack sites |
81 | sites -> shortCallStack sites | 84 | sites -> shortCallStack sites |
82 | 85 | ||
86 | |||
83 | instrumented :: ( HasCallStack, MonadBaseControl IO m ) => | 87 | instrumented :: ( HasCallStack, MonadBaseControl IO m ) => |
84 | (m () -> m ThreadId) -> m () -> m ThreadId | 88 | (((forall a. m a -> m a) -> m ()) -> m ThreadId) -> m () -> m ThreadId |
85 | instrumented rawFork action = do | 89 | instrumented rawFork action = do |
86 | mvar <- newEmptyMVar | 90 | mvar <- newEmptyMVar |
87 | t <- rawFork $ do | 91 | tm <- liftBase getCurrentTime |
88 | tid <- myThreadId | 92 | t <- mask_ $ rawFork $ \unmask -> do |
89 | tm <- liftBase getCurrentTime | 93 | tid <- myThreadId |
90 | bracket_ (modifyThreads $! \ts -> Map.union ts (Map.singleton tid (PerThread (defaultLabel callStack) tm))) | 94 | let scrapIt = do takeMVar mvar |
91 | (return ()) | 95 | modifyThreads $! Map.delete tid |
92 | $ do catch action $ \e -> case fromException e of | 96 | io <- catch (unmask action >> return scrapIt) $ \e -> case fromException e of |
93 | Just ThreadKilled -> return () | 97 | Just ThreadKilled -> return scrapIt |
94 | Nothing -> liftBase $ do | 98 | Nothing -> liftBase $ do |
95 | bracket (takeMVar globals) | 99 | g <- takeMVar globals |
96 | (\g -> do | 100 | let l = concat [ show e |
97 | let l = concat [ show e | 101 | , " (" |
98 | , " (" | 102 | , maybe "" lbl $ Map.lookup tid (threads g) |
99 | , maybe "" lbl $ Map.lookup tid (threads g) | 103 | , ")" |
100 | , ")" | 104 | ] |
101 | ] | 105 | reportException g l |
102 | foldr seq (return ()) l | 106 | let l = concat [ show e |
103 | putMVar globals $! g { threads = Map.insert tid (PerThread l tm) $ threads g } | 107 | , " (" |
104 | throwIO e) | 108 | , maybe "" lbl $ Map.lookup tid (threads g) |
105 | (\g -> do | 109 | , ")" |
106 | let l = concat [ show e | 110 | ] |
107 | , " (" | 111 | foldr seq (return ()) l |
108 | , maybe "" lbl $ Map.lookup tid (threads g) | 112 | putMVar globals $! g { threads = Map.insert tid (PerThread l tm) $ threads g } |
109 | , ")" | 113 | return $ return () -- Remove the thread only if it terminated normally or was killed. |
110 | ] | 114 | io -- scrap record on normal termination |
111 | reportException g l) | 115 | liftBase $ labelThread_ t (defaultLabel callStack) tm |
112 | -- Remove the thread only if it terminated normally or was killed. | ||
113 | takeMVar mvar | ||
114 | modifyThreads $! Map.delete tid | ||
115 | liftBase $ labelThread_ t (defaultLabel callStack) | ||
116 | putMVar mvar () | 116 | putMVar mvar () |
117 | return t | 117 | return t |
118 | 118 | ||
119 | labelThread_ :: ThreadId -> String -> IO () | 119 | labelThread_ :: ThreadId -> String -> UTCTime -> IO () |
120 | labelThread_ tid s = do | 120 | labelThread_ tid s tm = do |
121 | foldr seq (return ()) s | 121 | foldr seq (return ()) s |
122 | GHC.labelThread tid s | 122 | GHC.labelThread tid s |
123 | tm <- liftBase getCurrentTime | ||
124 | let updateIt (Just pt) = Just $ pt { lbl = s } | 123 | let updateIt (Just pt) = Just $ pt { lbl = s } |
125 | updateIt Nothing = Just $ PerThread s tm | 124 | updateIt Nothing = Just $ PerThread s tm |
126 | modifyThreads $! Map.alter updateIt tid | 125 | modifyThreads $! Map.alter updateIt tid |