|
1 | 1 | -- We deliberately want to ensure the function we add to the rule database
|
2 | 2 | -- has the constraints we need on it when we get it out.
|
3 | 3 | {-# OPTIONS_GHC -Wno-redundant-constraints #-}
|
4 |
| -{-# LANGUAGE LambdaCase #-} |
5 |
| -{-# LANGUAGE NamedFieldPuns #-} |
6 |
| -{-# LANGUAGE RecordWildCards #-} |
7 |
| -{-# LANGUAGE ScopedTypeVariables #-} |
8 |
| -{-# LANGUAGE TupleSections #-} |
9 |
| -{-# LANGUAGE TypeFamilies #-} |
10 |
| -{-# LANGUAGE ViewPatterns #-} |
| 4 | +{-# LANGUAGE DeriveFunctor #-} |
| 5 | +{-# LANGUAGE DerivingStrategies #-} |
| 6 | +{-# LANGUAGE GeneralizedNewtypeDeriving #-} |
| 7 | +{-# LANGUAGE LambdaCase #-} |
| 8 | +{-# LANGUAGE NamedFieldPuns #-} |
| 9 | +{-# LANGUAGE RecordWildCards #-} |
| 10 | +{-# LANGUAGE ScopedTypeVariables #-} |
| 11 | +{-# LANGUAGE TupleSections #-} |
| 12 | +{-# LANGUAGE TypeFamilies #-} |
| 13 | +{-# LANGUAGE ViewPatterns #-} |
11 | 14 |
|
12 |
| -module Development.IDE.Graph.Internal.Database where |
| 15 | +module Development.IDE.Graph.Internal.Database (newDatabase, incDatabase, build) where |
13 | 16 |
|
14 | 17 | import Control.Concurrent.Async
|
15 | 18 | import Control.Concurrent.Extra
|
16 | 19 | import Control.Exception
|
17 | 20 | import Control.Monad
|
| 21 | +import Control.Monad.IO.Class (MonadIO (liftIO)) |
18 | 22 | import Control.Monad.Trans.Class (lift)
|
19 | 23 | import Control.Monad.Trans.Reader
|
20 | 24 | import qualified Control.Monad.Trans.State.Strict as State
|
@@ -76,81 +80,84 @@ build
|
76 | 80 | :: forall key value . (Shake.RuleResult key ~ value, Typeable key, Show key, Hashable key, Eq key, Typeable value)
|
77 | 81 | => Database -> [key] -> IO ([Id], [value])
|
78 | 82 | build db keys = do
|
79 |
| - (ids, vs) <- fmap unzip $ builder db $ map (Right . Key) keys |
| 83 | + (ids, vs) <- runAIO $ fmap unzip $ either return liftIO =<< builder db (map (Right . Key) keys) |
80 | 84 | pure (ids, map (asV . resultValue) vs)
|
81 | 85 | where
|
82 | 86 | asV :: Value -> value
|
83 | 87 | asV (Value x) = unwrapDynamic x
|
84 | 88 |
|
85 |
| --- | Build a list of keys in parallel |
| 89 | +-- | Build a list of keys and return their results. |
| 90 | +-- If none of the keys are dirty, we can return the results immediately. |
| 91 | +-- Otherwise, a blocking computation is returned *which must be evaluated asynchronously* to avoid deadlock. |
86 | 92 | builder
|
87 |
| - :: Database -> [Either Id Key] -> IO [(Id, Result)] |
| 93 | + :: Database -> [Either Id Key] -> AIO (Either [(Id, Result)] (IO [(Id, Result)])) |
88 | 94 | builder db@Database{..} keys = do
|
89 |
| - -- Async things that I own and am responsible for killing |
90 |
| - ownedAsync <- newIORef [] |
91 |
| - flip onException (cleanupAsync ownedAsync) $ do |
92 |
| - |
93 | 95 | -- Things that I need to force before my results are ready
|
94 |
| - toForce <- newIORef [] |
| 96 | + toForce <- liftIO $ newIORef [] |
95 | 97 |
|
96 |
| - results <- withLock databaseLock $ do |
97 |
| - forM keys $ \idKey -> do |
| 98 | + results <- withLockAIO databaseLock $ do |
| 99 | + flip traverse keys $ \idKey -> do |
98 | 100 | -- Resolve the id
|
99 | 101 | id <- case idKey of
|
100 | 102 | Left id -> pure id
|
101 | 103 | Right key -> do
|
102 |
| - ids <- readIORef databaseIds |
| 104 | + ids <- liftIO $ readIORef databaseIds |
103 | 105 | case Intern.lookup key ids of
|
104 | 106 | Just v -> pure v
|
105 | 107 | Nothing -> do
|
106 | 108 | (ids, id) <- pure $ Intern.add key ids
|
107 |
| - writeIORef' databaseIds ids |
| 109 | + liftIO $ writeIORef' databaseIds ids |
108 | 110 | return id
|
109 | 111 |
|
110 | 112 | -- Spawn the id if needed
|
111 |
| - status <- Ids.lookup databaseValues id |
| 113 | + status <- liftIO $ Ids.lookup databaseValues id |
112 | 114 | val <- case fromMaybe (fromRight undefined idKey, Dirty Nothing) status of
|
113 | 115 | (_, Clean r) -> pure r
|
114 | 116 | (_, Running act _) -> do
|
115 |
| - -- we promise to force everything in todo before reading the results |
116 |
| - -- so the following unsafePerformIO isn't actually unsafe |
117 | 117 | let (force, val) = splitIO act
|
118 |
| - modifyIORef toForce (force:) |
| 118 | + liftIO $ modifyIORef toForce (force:) |
119 | 119 | pure val
|
120 | 120 | (key, Dirty s) -> do
|
121 |
| - -- Important we don't lose any Async things we create |
122 |
| - act <- uninterruptibleMask $ \restore -> do |
123 |
| - -- the child actions should always be spawned unmasked |
124 |
| - -- or they can't be killed |
125 |
| - async <- async $ restore $ check db key id s |
126 |
| - modifyIORef ownedAsync (async:) |
127 |
| - pure $ wait async |
128 |
| - Ids.insert databaseValues id (key, Running act s) |
| 121 | + act <- join <$> unliftAIO (refresh db key id s) |
| 122 | + liftIO $ Ids.insert databaseValues id (key, Running act s) |
129 | 123 | let (force, val) = splitIO act
|
130 |
| - modifyIORef toForce (force:) |
| 124 | + liftIO $ modifyIORef toForce (force:) |
131 | 125 | pure val
|
132 |
| - |
133 | 126 | pure (id, val)
|
134 | 127 |
|
135 |
| - sequence_ =<< readIORef toForce |
136 |
| - pure results |
| 128 | + toForceList <- liftIO $ readIORef toForce |
| 129 | + case toForceList of |
| 130 | + [] -> return $ Left results |
| 131 | + _ -> return $ Right $ do |
| 132 | + sequence_ toForceList |
| 133 | + pure results |
137 | 134 |
|
138 |
| -cleanupAsync :: IORef [Async a] -> IO () |
139 |
| -cleanupAsync ref = mapConcurrently_ uninterruptibleCancel =<< readIORef ref |
140 |
| - |
141 |
| --- | Check if we need to run the database. |
142 |
| -check :: Database -> Key -> Id -> Maybe Result -> IO Result |
143 |
| -check db key id result@(Just me@Result{resultDeps=Just deps}) = do |
| 135 | +-- | Refresh a key: |
| 136 | +-- * If no dirty dependencies and we have evaluated the key previously, then we refresh it in the current thread. |
| 137 | +-- This assumes that the implementation will be a lookup |
| 138 | +-- * Otherwise, we spawn a new thread to refresh the dirty deps (if any) and the key itself |
| 139 | +refresh :: Database -> Key -> Id -> Maybe Result -> AIO (IO Result) |
| 140 | +refresh db key id result@(Just me@Result{resultDeps=Just deps}) = do |
144 | 141 | res <- builder db $ map Left deps
|
145 |
| - let dirty = any (\(_,dep) -> resultBuilt me < resultChanged dep) res |
146 |
| - let mode = if dirty then Shake.RunDependenciesChanged else Shake.RunDependenciesSame |
147 |
| - spawn db key id mode result |
148 |
| -check db key id result = spawn db key id Shake.RunDependenciesChanged result |
| 142 | + case res of |
| 143 | + Left res -> |
| 144 | + if isDirty res |
| 145 | + then asyncWithCleanUp $ liftIO $ compute db key id Shake.RunDependenciesChanged result |
| 146 | + else pure $ compute db key id Shake.RunDependenciesSame result |
| 147 | + Right iores -> asyncWithCleanUp $ liftIO $ do |
| 148 | + res <- iores |
| 149 | + let mode = if isDirty res then Shake.RunDependenciesChanged else Shake.RunDependenciesSame |
| 150 | + compute db key id mode result |
| 151 | + where |
| 152 | + isDirty = any (\(_,dep) -> resultBuilt me < resultChanged dep) |
| 153 | + |
| 154 | +refresh db key id result = |
| 155 | + asyncWithCleanUp $ liftIO $ compute db key id Shake.RunDependenciesChanged result |
149 | 156 |
|
150 | 157 |
|
151 |
| --- | Spawn a new computation to run the action. |
152 |
| -spawn :: Database -> Key -> Id -> Shake.RunMode -> Maybe Result -> IO Result |
153 |
| -spawn db@Database{..} key id mode result = do |
| 158 | +-- | Compute a key. |
| 159 | +compute :: Database -> Key -> Id -> Shake.RunMode -> Maybe Result -> IO Result |
| 160 | +compute db@Database{..} key id mode result = do |
154 | 161 | let act = runRule databaseRules key (fmap resultData result) mode
|
155 | 162 | deps <- newIORef $ Just []
|
156 | 163 | (execution, Shake.RunResult{..}) <-
|
@@ -218,6 +225,34 @@ transitiveDirtySet database = flip State.execStateT Set.empty . traverse_ loop
|
218 | 225 | next <- lift $ getReverseDependencies database x
|
219 | 226 | traverse_ loop (maybe mempty Set.toList next)
|
220 | 227 |
|
| 228 | +-- | IO extended to track created asyncs to clean them up when the thread is killed, |
| 229 | +-- generalizing 'withAsync' |
| 230 | +newtype AIO a = AIO { unAIO :: ReaderT (IORef [Async ()]) IO a } |
| 231 | + deriving newtype (Applicative, Functor, Monad, MonadIO) |
221 | 232 |
|
222 |
| -idFromInt :: Set.Key -> Id |
223 |
| -idFromInt = id |
| 233 | +runAIO :: AIO a -> IO a |
| 234 | +runAIO (AIO act) = do |
| 235 | + asyncs <- newIORef [] |
| 236 | + runReaderT act asyncs `onException` cleanupAsync asyncs |
| 237 | + |
| 238 | +asyncWithCleanUp :: AIO a -> AIO (IO a) |
| 239 | +asyncWithCleanUp act = do |
| 240 | + st <- AIO ask |
| 241 | + io <- unliftAIO act |
| 242 | + liftIO $ uninterruptibleMask $ \restore -> do |
| 243 | + a <- async $ restore io |
| 244 | + modifyIORef st (void a :) |
| 245 | + return $ wait a |
| 246 | + |
| 247 | +withLockAIO :: Lock -> AIO a -> AIO a |
| 248 | +withLockAIO lock act = do |
| 249 | + io <- unliftAIO act |
| 250 | + liftIO $ withLock lock io |
| 251 | + |
| 252 | +unliftAIO :: AIO a -> AIO (IO a) |
| 253 | +unliftAIO act = do |
| 254 | + st <- AIO ask |
| 255 | + return $ runReaderT (unAIO act) st |
| 256 | + |
| 257 | +cleanupAsync :: IORef [Async a] -> IO () |
| 258 | +cleanupAsync ref = mapConcurrently_ uninterruptibleCancel =<< readIORef ref |
0 commit comments