hls-graph 1.5.1.1 → 1.6.0.0
raw patch · 15 files changed
+640/−598 lines, 15 filesdep +focusdep +list-tdep +stmPVP ok
version bump matches the API change (PVP)
Dependencies added: focus, list-t, stm, stm-containers
API changes (from Hackage documentation)
- Development.IDE.Graph.Internal.Ids: data Ids a
- Development.IDE.Graph.Internal.Ids: elems :: Ids a -> IO [a]
- Development.IDE.Graph.Internal.Ids: empty :: IO (Ids a)
- Development.IDE.Graph.Internal.Ids: forCopy :: Ids a -> (a -> b) -> IO (Ids b)
- Development.IDE.Graph.Internal.Ids: forMutate :: Ids a -> (Id -> a -> a) -> IO ()
- Development.IDE.Graph.Internal.Ids: forWithKeyM_ :: Ids a -> (Id -> a -> IO ()) -> IO ()
- Development.IDE.Graph.Internal.Ids: fromList :: [a] -> IO (Ids a)
- Development.IDE.Graph.Internal.Ids: insert :: Ids a -> Id -> a -> IO ()
- Development.IDE.Graph.Internal.Ids: lookup :: Ids a -> Id -> IO (Maybe a)
- Development.IDE.Graph.Internal.Ids: null :: Ids a -> IO Bool
- Development.IDE.Graph.Internal.Ids: size :: Ids a -> IO Int
- Development.IDE.Graph.Internal.Ids: sizeUpperBound :: Ids a -> IO Int
- Development.IDE.Graph.Internal.Ids: toList :: Ids a -> IO [(Id, a)]
- Development.IDE.Graph.Internal.Ids: toMap :: Ids a -> IO (HashMap Id a)
- Development.IDE.Graph.Internal.Ids: type Id = Int
- Development.IDE.Graph.Internal.Intern: add :: (Eq a, Hashable a) => a -> Intern a -> (Intern a, Id)
- Development.IDE.Graph.Internal.Intern: data Intern a
- Development.IDE.Graph.Internal.Intern: empty :: Intern a
- Development.IDE.Graph.Internal.Intern: fromList :: (Eq a, Hashable a) => [(a, Id)] -> Intern a
- Development.IDE.Graph.Internal.Intern: insert :: (Eq a, Hashable a) => a -> Id -> Intern a -> Intern a
- Development.IDE.Graph.Internal.Intern: lookup :: (Eq a, Hashable a) => a -> Intern a -> Maybe Id
- Development.IDE.Graph.Internal.Intern: toList :: Intern a -> [(a, Id)]
- Development.IDE.Graph.Internal.Intern: type Id = Int
- Development.IDE.Graph.Internal.Types: [databaseIds] :: Database -> !IORef (Intern Key)
- Development.IDE.Graph.Internal.Types: [databaseLock] :: Database -> !Lock
- Development.IDE.Graph.Internal.Types: [databaseReverseDepsLock] :: Database -> !Lock
- Development.IDE.Graph.Internal.Types: [databaseReverseDeps] :: Database -> !Ids IntSet
+ Control.Concurrent.STM.Stats: atomically :: STM a -> IO a
+ Control.Concurrent.STM.Stats: atomicallyNamed :: String -> STM a -> IO a
+ Control.Concurrent.STM.Stats: catchSTM :: Exception e => STM a -> (e -> STM a) -> STM a
+ Control.Concurrent.STM.Stats: check :: Bool -> STM ()
+ Control.Concurrent.STM.Stats: cloneTChan :: TChan a -> STM (TChan a)
+ Control.Concurrent.STM.Stats: data STM a
+ Control.Concurrent.STM.Stats: data TArray i e
+ Control.Concurrent.STM.Stats: data TBQueue a
+ Control.Concurrent.STM.Stats: data TChan a
+ Control.Concurrent.STM.Stats: data TMVar a
+ Control.Concurrent.STM.Stats: data TQueue a
+ Control.Concurrent.STM.Stats: data TVar a
+ Control.Concurrent.STM.Stats: dumpSTMStats :: IO ()
+ Control.Concurrent.STM.Stats: dupTChan :: TChan a -> STM (TChan a)
+ Control.Concurrent.STM.Stats: flushTBQueue :: TBQueue a -> STM [a]
+ Control.Concurrent.STM.Stats: flushTQueue :: TQueue a -> STM [a]
+ Control.Concurrent.STM.Stats: getSTMStats :: IO (Map String (Int, Int))
+ Control.Concurrent.STM.Stats: isEmptyTBQueue :: TBQueue a -> STM Bool
+ Control.Concurrent.STM.Stats: isEmptyTChan :: TChan a -> STM Bool
+ Control.Concurrent.STM.Stats: isEmptyTMVar :: TMVar a -> STM Bool
+ Control.Concurrent.STM.Stats: isEmptyTQueue :: TQueue a -> STM Bool
+ Control.Concurrent.STM.Stats: isFullTBQueue :: TBQueue a -> STM Bool
+ Control.Concurrent.STM.Stats: lengthTBQueue :: TBQueue a -> STM Natural
+ Control.Concurrent.STM.Stats: mkWeakTMVar :: TMVar a -> IO () -> IO (Weak (TMVar a))
+ Control.Concurrent.STM.Stats: mkWeakTVar :: TVar a -> IO () -> IO (Weak (TVar a))
+ Control.Concurrent.STM.Stats: modifyTVar :: TVar a -> (a -> a) -> STM ()
+ Control.Concurrent.STM.Stats: modifyTVar' :: TVar a -> (a -> a) -> STM ()
+ Control.Concurrent.STM.Stats: newBroadcastTChan :: STM (TChan a)
+ Control.Concurrent.STM.Stats: newBroadcastTChanIO :: IO (TChan a)
+ Control.Concurrent.STM.Stats: newEmptyTMVar :: STM (TMVar a)
+ Control.Concurrent.STM.Stats: newEmptyTMVarIO :: IO (TMVar a)
+ Control.Concurrent.STM.Stats: newTBQueue :: Natural -> STM (TBQueue a)
+ Control.Concurrent.STM.Stats: newTBQueueIO :: Natural -> IO (TBQueue a)
+ Control.Concurrent.STM.Stats: newTChan :: STM (TChan a)
+ Control.Concurrent.STM.Stats: newTChanIO :: IO (TChan a)
+ Control.Concurrent.STM.Stats: newTMVar :: a -> STM (TMVar a)
+ Control.Concurrent.STM.Stats: newTMVarIO :: a -> IO (TMVar a)
+ Control.Concurrent.STM.Stats: newTQueue :: STM (TQueue a)
+ Control.Concurrent.STM.Stats: newTQueueIO :: IO (TQueue a)
+ Control.Concurrent.STM.Stats: newTVar :: a -> STM (TVar a)
+ Control.Concurrent.STM.Stats: newTVarIO :: a -> IO (TVar a)
+ Control.Concurrent.STM.Stats: orElse :: STM a -> STM a -> STM a
+ Control.Concurrent.STM.Stats: peekTBQueue :: TBQueue a -> STM a
+ Control.Concurrent.STM.Stats: peekTChan :: TChan a -> STM a
+ Control.Concurrent.STM.Stats: peekTQueue :: TQueue a -> STM a
+ Control.Concurrent.STM.Stats: putTMVar :: TMVar a -> a -> STM ()
+ Control.Concurrent.STM.Stats: readTBQueue :: TBQueue a -> STM a
+ Control.Concurrent.STM.Stats: readTChan :: TChan a -> STM a
+ Control.Concurrent.STM.Stats: readTMVar :: TMVar a -> STM a
+ Control.Concurrent.STM.Stats: readTQueue :: TQueue a -> STM a
+ Control.Concurrent.STM.Stats: readTVar :: TVar a -> STM a
+ Control.Concurrent.STM.Stats: readTVarIO :: TVar a -> IO a
+ Control.Concurrent.STM.Stats: registerDelay :: Int -> IO (TVar Bool)
+ Control.Concurrent.STM.Stats: retry :: STM a
+ Control.Concurrent.STM.Stats: stateTVar :: TVar s -> (s -> (a, s)) -> STM a
+ Control.Concurrent.STM.Stats: swapTMVar :: TMVar a -> a -> STM a
+ Control.Concurrent.STM.Stats: swapTVar :: TVar a -> a -> STM a
+ Control.Concurrent.STM.Stats: takeTMVar :: TMVar a -> STM a
+ Control.Concurrent.STM.Stats: throwSTM :: Exception e => e -> STM a
+ Control.Concurrent.STM.Stats: tryPeekTBQueue :: TBQueue a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: tryPeekTChan :: TChan a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: tryPeekTQueue :: TQueue a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: tryPutTMVar :: TMVar a -> a -> STM Bool
+ Control.Concurrent.STM.Stats: tryReadTBQueue :: TBQueue a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: tryReadTChan :: TChan a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: tryReadTMVar :: TMVar a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: tryReadTQueue :: TQueue a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: tryTakeTMVar :: TMVar a -> STM (Maybe a)
+ Control.Concurrent.STM.Stats: unGetTBQueue :: TBQueue a -> a -> STM ()
+ Control.Concurrent.STM.Stats: unGetTChan :: TChan a -> a -> STM ()
+ Control.Concurrent.STM.Stats: unGetTQueue :: TQueue a -> a -> STM ()
+ Control.Concurrent.STM.Stats: writeTBQueue :: TBQueue a -> a -> STM ()
+ Control.Concurrent.STM.Stats: writeTChan :: TChan a -> a -> STM ()
+ Control.Concurrent.STM.Stats: writeTQueue :: TQueue a -> a -> STM ()
+ Control.Concurrent.STM.Stats: writeTVar :: TVar a -> a -> STM ()
+ Development.IDE.Graph.Internal.Types: KeyDetails :: !Status -> !HashSet Key -> KeyDetails
+ Development.IDE.Graph.Internal.Types: [keyReverseDeps] :: KeyDetails -> !HashSet Key
+ Development.IDE.Graph.Internal.Types: [keyStatus] :: KeyDetails -> !Status
+ Development.IDE.Graph.Internal.Types: [runningPrev] :: Status -> !Maybe Result
+ Development.IDE.Graph.Internal.Types: [runningResult] :: Status -> Result
+ Development.IDE.Graph.Internal.Types: [runningStep] :: Status -> !Step
+ Development.IDE.Graph.Internal.Types: [runningWait] :: Status -> !IO ()
+ Development.IDE.Graph.Internal.Types: data KeyDetails
+ Development.IDE.Graph.Internal.Types: getDatabaseValues :: Database -> IO [(Key, Status)]
+ Development.IDE.Graph.Internal.Types: onKeyReverseDeps :: (HashSet Key -> HashSet Key) -> KeyDetails -> KeyDetails
+ Development.IDE.Graph.Internal.Types: viewDirty :: Step -> Status -> Status
- Development.IDE.Graph.Internal.Database: build :: forall key value. (RuleResult key ~ value, Typeable key, Show key, Hashable key, Eq key, Typeable value) => Database -> [key] -> IO ([Id], [value])
+ Development.IDE.Graph.Internal.Database: build :: forall key value. (RuleResult key ~ value, Typeable key, Show key, Hashable key, Eq key, Typeable value) => Database -> [key] -> IO ([Key], [value])
- Development.IDE.Graph.Internal.Database: getDirtySet :: Database -> IO [(Id, (Key, Int))]
+ Development.IDE.Graph.Internal.Database: getDirtySet :: Database -> IO [(Key, Int)]
- Development.IDE.Graph.Internal.Types: AlwaysRerunDeps :: ![Id] -> ResultDeps
+ Development.IDE.Graph.Internal.Types: AlwaysRerunDeps :: ![Key] -> ResultDeps
- Development.IDE.Graph.Internal.Types: Database :: Dynamic -> TheRules -> !IORef Step -> !Lock -> !IORef (Intern Key) -> !Ids (Key, Status) -> !Ids IntSet -> !Lock -> Database
+ Development.IDE.Graph.Internal.Types: Database :: Dynamic -> TheRules -> !TVar Step -> !Map Key KeyDetails -> Database
- Development.IDE.Graph.Internal.Types: ResultDeps :: ![Id] -> ResultDeps
+ Development.IDE.Graph.Internal.Types: ResultDeps :: ![Key] -> ResultDeps
- Development.IDE.Graph.Internal.Types: Running :: IO () -> Result -> Maybe Result -> Status
+ Development.IDE.Graph.Internal.Types: Running :: !Step -> !IO () -> Result -> !Maybe Result -> Status
- Development.IDE.Graph.Internal.Types: [databaseStep] :: Database -> !IORef Step
+ Development.IDE.Graph.Internal.Types: [databaseStep] :: Database -> !TVar Step
- Development.IDE.Graph.Internal.Types: [databaseValues] :: Database -> !Ids (Key, Status)
+ Development.IDE.Graph.Internal.Types: [databaseValues] :: Database -> !Map Key KeyDetails
- Development.IDE.Graph.Internal.Types: getResultDepsDefault :: [Id] -> ResultDeps -> [Id]
+ Development.IDE.Graph.Internal.Types: getResultDepsDefault :: [Key] -> ResultDeps -> [Key]
- Development.IDE.Graph.Internal.Types: mapResultDeps :: ([Id] -> [Id]) -> ResultDeps -> ResultDeps
+ Development.IDE.Graph.Internal.Types: mapResultDeps :: ([Key] -> [Key]) -> ResultDeps -> ResultDeps
Files
- README.md +17/−0
- hls-graph.cabal +19/−5
- src/Control/Concurrent/STM/Stats.hs +185/−0
- src/Development/IDE/Graph.hs +0/−2
- src/Development/IDE/Graph/Classes.hs +8/−8
- src/Development/IDE/Graph/Database.hs +6/−6
- src/Development/IDE/Graph/Internal/Action.hs +137/−138
- src/Development/IDE/Graph/Internal/Database.hs +129/−124
- src/Development/IDE/Graph/Internal/Ids.hs +0/−160
- src/Development/IDE/Graph/Internal/Intern.hs +0/−41
- src/Development/IDE/Graph/Internal/Options.hs +0/−2
- src/Development/IDE/Graph/Internal/Paths.hs +7/−1
- src/Development/IDE/Graph/Internal/Profile.hs +21/−24
- src/Development/IDE/Graph/Internal/Rules.hs +58/−60
- src/Development/IDE/Graph/Internal/Types.hs +53/−27
+ README.md view
@@ -0,0 +1,17 @@+# hls-graph - a limited reimplementation of Shake for in-memory build graphs++`ghcide` was originally built on top of [Shake](http://shakebuild.com), a Haskell build system. Nowadays Shake has been replaced by a special purpose implementation of a build graph called hls-graph, which drops all the persistency features in exchange for simplicity and performance.++Features:++* Dynamic dependencies+* User defined rules (there are no predefined File rules as in Shake)+* Build reports (a la Shake profiling)+* "Reactive" change tracking for minimal rebuilds (not available in Shake)++What's missing:++* Persistence+* A default set of rules for file system builds+* A testsuite+* General purpose application - many design decisions make assumptions specific to ghcide
hls-graph.cabal view
@@ -1,16 +1,16 @@ cabal-version: 2.4 name: hls-graph-version: 1.5.1.1+version: 1.6.0.0 synopsis: Haskell Language Server internal graph API description:- Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>+ Please see the README on GitHub at <https://github.com/haskell/haskell-language-server/tree/master/hls-graph#readme> homepage: https://github.com/haskell/haskell-language-server#readme bug-reports: https://github.com/haskell/haskell-language-server/issues license: Apache-2.0 license-file: LICENSE author: The Haskell IDE Team-maintainer: alan.zimm@gmail.com+maintainer: The Haskell IDE Team copyright: The Haskell IDE Team category: Development build-type: Simple@@ -18,6 +18,9 @@ html/profile.html html/shake.js +extra-source-files:+ README.md+ flag pedantic description: Enable -Werror default: False@@ -28,12 +31,18 @@ manual: True description: Embed data files into the shake library +flag stm-stats+ default: False+ manual: True+ description: Collect STM transaction stats+ source-repository head type: git location: https://github.com/haskell/haskell-language-server library exposed-modules:+ Control.Concurrent.STM.Stats Development.IDE.Graph Development.IDE.Graph.Classes Development.IDE.Graph.Database@@ -42,8 +51,6 @@ Development.IDE.Graph.Internal.Options Development.IDE.Graph.Internal.Rules Development.IDE.Graph.Internal.Database- Development.IDE.Graph.Internal.Ids- Development.IDE.Graph.Internal.Intern Development.IDE.Graph.Internal.Paths Development.IDE.Graph.Internal.Profile Development.IDE.Graph.Internal.Types@@ -63,11 +70,15 @@ , exceptions , extra , filepath+ , focus , hashable , js-dgtable , js-flot , js-jquery+ , list-t , primitive+ , stm+ , stm-containers , time , transformers , unordered-containers@@ -77,6 +88,9 @@ build-depends: file-embed >= 0.0.11, template-haskell+ if flag(stm-stats)+ cpp-options: -DSTM_STATS+ ghc-options: -Wall -Wredundant-constraints -Wno-name-shadowing
+ src/Control/Concurrent/STM/Stats.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+#ifdef STM_STATS+{-# LANGUAGE RecordWildCards #-}+#endif+module Control.Concurrent.STM.Stats+ ( atomicallyNamed+ , atomically+ , getSTMStats+ , dumpSTMStats+ , module Control.Concurrent.STM+ ) where++import Control.Concurrent.STM hiding (atomically)+import qualified Control.Concurrent.STM as STM+import Data.Map (Map)+#ifdef STM_STATS+import Control.Exception (BlockedIndefinitelyOnSTM, Exception,+ catch, throwIO)+import Control.Monad+import Data.IORef+import qualified Data.Map.Strict as M+import Data.Time (getCurrentTime)+import Data.Typeable (Typeable)+import GHC.Conc (unsafeIOToSTM)+import System.IO+import System.IO.Unsafe+import Text.Printf+#endif++atomicallyNamed :: String -> STM a -> IO a+atomically :: STM a -> IO a+dumpSTMStats :: IO ()+getSTMStats :: IO (Map String (Int,Int))++#ifndef STM_STATS++getSTMStats = pure mempty+atomicallyNamed _ = atomically+dumpSTMStats = pure ()+atomically = STM.atomically++#else+-- adapted from the STM.Stats package++atomicallyNamed = trackNamedSTM+atomically = trackSTM++-- | Global state, seems to be unavoidable here.+globalRetryCountMap :: IORef (Map String (Int,Int))+globalRetryCountMap = unsafePerformIO (newIORef M.empty)+{-# NOINLINE globalRetryCountMap #-}+++-- | For the most general transaction tracking function, 'trackSTMConf', all+-- settings can be configured using a 'TrackSTMConf' value.+data TrackSTMConf = TrackSTMConf+ { tryThreshold :: Maybe Int+ -- ^ If the number of retries of one transaction run reaches this+ -- count, a warning is issued at runtime. If set to @Nothing@, disables the warnings completely.+ , globalTheshold :: Maybe Int+ -- ^ If the total number of retries of one named transaction reaches+ -- this count, a warning is issued. If set to @Nothing@, disables the+ -- warnings completely.+ , extendException :: Bool+ -- ^ If this is set, a 'BlockedIndefinitelyOnSTM' exception is replaced+ -- by a 'BlockedIndefinitelyOnNamedSTM' exception, carrying the name of+ -- the exception.+ , warnFunction :: String -> IO ()+ -- ^ Function to call when a warning is to be emitted.+ , warnInSTMFunction :: String -> IO ()+ -- ^ Function to call when a warning is to be emitted during an STM+ -- transaction. This is possibly dangerous, see the documentation to+ -- 'unsafeIOToSTM', but can be useful to detect transactions that keep+ -- retrying forever.+ }++-- | The default settings are:+--+-- > defaultTrackSTMConf = TrackSTMConf+-- > { tryThreshold = Just 10+-- > , globalTheshold = Just 3000+-- > , exception = True+-- > , warnFunction = hPutStrLn stderr+-- > , warnInSTMFunction = \_ -> return ()+-- > }+defaultTrackSTMConf :: TrackSTMConf+defaultTrackSTMConf = TrackSTMConf+ { tryThreshold = Just 10+ , globalTheshold = Just 3000+ , extendException = True+ , warnFunction = hPutStrLn stderr+ , warnInSTMFunction = \_ -> return ()+ }++-- | A drop-in replacement for 'atomically'. The statistics will list this, and+-- all other unnamed transactions, as \"@_anonymous_@\" and+-- 'BlockedIndefinitelyOnSTM' exceptions will not be replaced.+-- See below for variants that give more control over the statistics and+-- generated warnings.+trackSTM :: STM a -> IO a+trackSTM = trackSTMConf defaultTrackSTMConf { extendException = False } "_anonymous_"++-- | Run 'atomically' and collect the retry statistics under the given name and using the default configuration, 'defaultTrackSTMConf'.+trackNamedSTM :: String -> STM a -> IO a+trackNamedSTM = trackSTMConf defaultTrackSTMConf++-- | Run 'atomically' and collect the retry statistics under the given name,+-- while issuing warnings when the configured thresholds are exceeded.+trackSTMConf :: TrackSTMConf -> String -> STM a -> IO a+trackSTMConf (TrackSTMConf {..}) name txm = do+ counter <- newIORef 0+ let wrappedTx =+ do unsafeIOToSTM $ do+ i <- atomicModifyIORef' counter incCounter+ when (warnPred i) $+ warnInSTMFunction $ msgPrefix ++ " reached try count of " ++ show i+ txm+ res <- if extendException+ then STM.atomically wrappedTx+ `catch` (\(_::BlockedIndefinitelyOnSTM) ->+ throwIO (BlockedIndefinitelyOnNamedSTM name))+ else STM.atomically wrappedTx+ i <- readIORef counter+ doMB tryThreshold $ \threshold ->+ when (i > threshold) $+ warnFunction $ msgPrefix ++ " finished after " ++ show (i-1) ++ " retries"+ incGlobalRetryCount (i - 1)+ return res+ where+ doMB Nothing _ = return ()+ doMB (Just x) m = m x+ incCounter i = let j = i + 1 in (j, j)+ warnPred j = case tryThreshold of+ Nothing -> False+ Just n -> j >= 2*n && (j >= 4 * n || j `mod` (2 * n) == 0)+ msgPrefix = "STM transaction " ++ name+ incGlobalRetryCount i = do+ (k,k') <- atomicModifyIORef' globalRetryCountMap $ \m ->+ let (oldVal, m') = M.insertLookupWithKey+ (\_ (a1,b1) (a2,b2) -> ((,) $! a1+a2) $! b1+b2)+ name+ (1,i)+ m+ in (m', let j = maybe 0 snd oldVal in (j,j+i))+ doMB globalTheshold $ \globalRetryThreshold ->+ when (k `div` globalRetryThreshold /= k' `div` globalRetryThreshold) $+ warnFunction $ msgPrefix ++ " reached global retry count of " ++ show k'++-- | If 'extendException' is set (which is the case with 'trackNamedSTM'), an+-- occurrence of 'BlockedIndefinitelyOnSTM' is replaced by+-- 'BlockedIndefinitelyOnNamedSTM', carrying the name of the transaction and+-- thus giving more helpful error messages.+newtype BlockedIndefinitelyOnNamedSTM = BlockedIndefinitelyOnNamedSTM String+ deriving (Typeable)++instance Show BlockedIndefinitelyOnNamedSTM where+ showsPrec _ (BlockedIndefinitelyOnNamedSTM name) =+ showString $ "thread blocked indefinitely in STM transaction" ++ name++instance Exception BlockedIndefinitelyOnNamedSTM++++-- | Fetches the current transaction statistics data.+--+-- The map maps transaction names to counts of transaction commits and+-- transaction retries.+getSTMStats = readIORef globalRetryCountMap++-- | Dumps the current transaction statistics data to 'System.IO.stderr'.+dumpSTMStats = do+ stats <- getSTMStats+ time <- show <$> getCurrentTime+ hPutStrLn stderr $ "STM transaction statistics (" ++ time ++ "):"+ sequence_ $+ hPrintf stderr "%-22s %10s %10s %10s\n" "Transaction" "Commits" "Retries" "Ratio" :+ [ hPrintf stderr "%-22s %10d %10d %10.2f\n" name commits retries ratio+ | (name,(commits,retries)) <- M.toList stats+ , commits > 0 -- safeguard+ , let ratio = fromIntegral retries / fromIntegral commits :: Double+ ]+++#endif
src/Development/IDE/Graph.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE PatternSynonyms #-}- module Development.IDE.Graph( shakeOptions, Rules,
src/Development/IDE/Graph/Classes.hs view
@@ -1,8 +1,8 @@--module Development.IDE.Graph.Classes(- Show(..), Typeable, Eq(..), Hashable(..), NFData(..)- ) where--import Control.DeepSeq-import Data.Hashable-import Data.Typeable+ +module Development.IDE.Graph.Classes( + Show(..), Typeable, Eq(..), Hashable(..), NFData(..) + ) where + +import Control.DeepSeq +import Data.Hashable +import Data.Typeable
src/Development/IDE/Graph/Database.hs view
@@ -12,13 +12,12 @@ shakeGetDirtySet, shakeGetCleanKeys ,shakeGetBuildEdges) where+import Control.Concurrent.STM.Stats (readTVarIO) import Data.Dynamic-import Data.IORef (readIORef) import Data.Maybe import Development.IDE.Graph.Classes () import Development.IDE.Graph.Internal.Action import Development.IDE.Graph.Internal.Database-import qualified Development.IDE.Graph.Internal.Ids as Ids import Development.IDE.Graph.Internal.Options import Development.IDE.Graph.Internal.Profile (writeProfile) import Development.IDE.Graph.Internal.Rules@@ -45,18 +44,19 @@ -- | Returns the set of dirty keys annotated with their age (in # of builds) shakeGetDirtySet :: ShakeDatabase -> IO [(Key, Int)] shakeGetDirtySet (ShakeDatabase _ _ db) =- fmap snd <$> Development.IDE.Graph.Internal.Database.getDirtySet db+ Development.IDE.Graph.Internal.Database.getDirtySet db -- | Returns the build number shakeGetBuildStep :: ShakeDatabase -> IO Int shakeGetBuildStep (ShakeDatabase _ _ db) = do- Step s <- readIORef $ databaseStep db+ Step s <- readTVarIO $ databaseStep db return s -- Only valid if we never pull on the results, which we don't unvoid :: Functor m => m () -> m a unvoid = fmap undefined +-- | Assumes that the database is not running a build shakeRunDatabaseForKeys :: Maybe [Key] -- ^ Set of keys changed since last run. 'Nothing' means everything has changed@@ -75,12 +75,12 @@ -- | Returns the clean keys in the database shakeGetCleanKeys :: ShakeDatabase -> IO [(Key, Result )] shakeGetCleanKeys (ShakeDatabase _ _ db) = do- keys <- Ids.elems $ databaseValues db+ keys <- getDatabaseValues db return [ (k,res) | (k, Clean res) <- keys] -- | Returns the total count of edges in the build graph shakeGetBuildEdges :: ShakeDatabase -> IO Int shakeGetBuildEdges (ShakeDatabase _ _ db) = do- keys <- Ids.elems $ databaseValues db+ keys <- getDatabaseValues db let ress = mapMaybe (getResult . snd) keys return $ sum $ map (length . getResultDepsDefault [] . resultDeps) ress
src/Development/IDE/Graph/Internal/Action.hs view
@@ -1,138 +1,137 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}--module Development.IDE.Graph.Internal.Action-( ShakeValue-, actionFork-, actionBracket-, actionCatch-, actionFinally-, alwaysRerun-, apply1-, apply-, parallel-, reschedule-, runActions-, Development.IDE.Graph.Internal.Action.getDirtySet-, getKeysAndVisitedAge-) where--import Control.Concurrent.Async-import Control.Exception-import Control.Monad.IO.Class-import Control.Monad.Trans.Class-import Control.Monad.Trans.Reader-import Data.IORef-import Development.IDE.Graph.Classes-import Development.IDE.Graph.Internal.Database-import Development.IDE.Graph.Internal.Rules (RuleResult)-import Development.IDE.Graph.Internal.Types-import System.Exit--type ShakeValue a = (Show a, Typeable a, Eq a, Hashable a, NFData a)---- | Always rerun this rule when dirty, regardless of the dependencies.-alwaysRerun :: Action ()-alwaysRerun = do- ref <- Action $ asks actionDeps- liftIO $ modifyIORef ref (AlwaysRerunDeps [] <>)---- No-op for now-reschedule :: Double -> Action ()-reschedule _ = pure ()--parallel :: [Action a] -> Action [a]-parallel [] = pure []-parallel [x] = fmap (:[]) x-parallel xs = do- a <- Action ask- deps <- liftIO $ readIORef $ actionDeps a- case deps of- UnknownDeps ->- -- if we are already in the rerun mode, nothing we do is going to impact our state- liftIO $ mapConcurrently (ignoreState a) xs- deps -> do- (newDeps, res) <- liftIO $ unzip <$> mapConcurrently (usingState a) xs- liftIO $ writeIORef (actionDeps a) $ mconcat $ deps : newDeps- pure res- where- usingState a x = do- ref <- newIORef mempty- res <- runReaderT (fromAction x) a{actionDeps=ref}- deps <- readIORef ref- pure (deps, res)--ignoreState :: SAction -> Action b -> IO b-ignoreState a x = do- ref <- newIORef mempty- runReaderT (fromAction x) a{actionDeps=ref}--actionFork :: Action a -> (Async a -> Action b) -> Action b-actionFork act k = do- a <- Action ask- deps <- liftIO $ readIORef $ actionDeps a- let db = actionDatabase a- case deps of- UnknownDeps -> do- -- if we are already in the rerun mode, nothing we do is going to impact our state- [res] <- liftIO $ withAsync (ignoreState a act) $ \as -> runActions db [k as]- return res- _ ->- error "please help me"--isAsyncException :: SomeException -> Bool-isAsyncException e- | Just (_ :: AsyncCancelled) <- fromException e = True- | Just (_ :: AsyncException) <- fromException e = True- | Just (_ :: ExitCode) <- fromException e = True- | otherwise = False---actionCatch :: Exception e => Action a -> (e -> Action a) -> Action a-actionCatch a b = do- v <- Action ask- Action $ lift $ catchJust f (runReaderT (fromAction a) v) (\x -> runReaderT (fromAction (b x)) v)- where- -- Catch only catches exceptions that were caused by this code, not those that- -- are a result of program termination- f e | isAsyncException e = Nothing- | otherwise = fromException e--actionBracket :: IO a -> (a -> IO b) -> (a -> Action c) -> Action c-actionBracket a b c = do- v <- Action ask- Action $ lift $ bracket a b (\x -> runReaderT (fromAction (c x)) v)--actionFinally :: Action a -> IO b -> Action a-actionFinally a b = do- v <- Action ask- Action $ lift $ finally (runReaderT (fromAction a) v) b--apply1 :: (RuleResult key ~ value, ShakeValue key, Typeable value) => key -> Action value-apply1 k = head <$> apply [k]--apply :: (RuleResult key ~ value, ShakeValue key, Typeable value) => [key] -> Action [value]-apply ks = do- db <- Action $ asks actionDatabase- (is, vs) <- liftIO $ build db ks- ref <- Action $ asks actionDeps- liftIO $ modifyIORef ref (ResultDeps is <>)- pure vs--runActions :: Database -> [Action a] -> IO [a]-runActions db xs = do- deps <- newIORef mempty- runReaderT (fromAction $ parallel xs) $ SAction db deps---- | Returns the set of dirty keys annotated with their age (in # of builds)-getDirtySet :: Action [(Key, Int)]-getDirtySet = do- db <- getDatabase- liftIO $ fmap snd <$> Development.IDE.Graph.Internal.Database.getDirtySet db--getKeysAndVisitedAge :: Action [(Key, Int)]-getKeysAndVisitedAge = do- db <- getDatabase- liftIO $ Development.IDE.Graph.Internal.Database.getKeysAndVisitAge db+{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} + +module Development.IDE.Graph.Internal.Action +( ShakeValue +, actionFork +, actionBracket +, actionCatch +, actionFinally +, alwaysRerun +, apply1 +, apply +, parallel +, reschedule +, runActions +, Development.IDE.Graph.Internal.Action.getDirtySet +, getKeysAndVisitedAge +) where + +import Control.Concurrent.Async +import Control.Exception +import Control.Monad.IO.Class +import Control.Monad.Trans.Class +import Control.Monad.Trans.Reader +import Data.IORef +import Development.IDE.Graph.Classes +import Development.IDE.Graph.Internal.Database +import Development.IDE.Graph.Internal.Rules (RuleResult) +import Development.IDE.Graph.Internal.Types +import System.Exit + +type ShakeValue a = (Show a, Typeable a, Eq a, Hashable a, NFData a) + +-- | Always rerun this rule when dirty, regardless of the dependencies. +alwaysRerun :: Action () +alwaysRerun = do + ref <- Action $ asks actionDeps + liftIO $ modifyIORef ref (AlwaysRerunDeps [] <>) + +-- No-op for now +reschedule :: Double -> Action () +reschedule _ = pure () + +parallel :: [Action a] -> Action [a] +parallel [] = pure [] +parallel [x] = fmap (:[]) x +parallel xs = do + a <- Action ask + deps <- liftIO $ readIORef $ actionDeps a + case deps of + UnknownDeps -> + -- if we are already in the rerun mode, nothing we do is going to impact our state + liftIO $ mapConcurrently (ignoreState a) xs + deps -> do + (newDeps, res) <- liftIO $ unzip <$> mapConcurrently (usingState a) xs + liftIO $ writeIORef (actionDeps a) $ mconcat $ deps : newDeps + pure res + where + usingState a x = do + ref <- newIORef mempty + res <- runReaderT (fromAction x) a{actionDeps=ref} + deps <- readIORef ref + pure (deps, res) + +ignoreState :: SAction -> Action b -> IO b +ignoreState a x = do + ref <- newIORef mempty + runReaderT (fromAction x) a{actionDeps=ref} + +actionFork :: Action a -> (Async a -> Action b) -> Action b +actionFork act k = do + a <- Action ask + deps <- liftIO $ readIORef $ actionDeps a + let db = actionDatabase a + case deps of + UnknownDeps -> do + -- if we are already in the rerun mode, nothing we do is going to impact our state + [res] <- liftIO $ withAsync (ignoreState a act) $ \as -> runActions db [k as] + return res + _ -> + error "please help me" + +isAsyncException :: SomeException -> Bool +isAsyncException e + | Just (_ :: AsyncCancelled) <- fromException e = True + | Just (_ :: AsyncException) <- fromException e = True + | Just (_ :: ExitCode) <- fromException e = True + | otherwise = False + + +actionCatch :: Exception e => Action a -> (e -> Action a) -> Action a +actionCatch a b = do + v <- Action ask + Action $ lift $ catchJust f (runReaderT (fromAction a) v) (\x -> runReaderT (fromAction (b x)) v) + where + -- Catch only catches exceptions that were caused by this code, not those that + -- are a result of program termination + f e | isAsyncException e = Nothing + | otherwise = fromException e + +actionBracket :: IO a -> (a -> IO b) -> (a -> Action c) -> Action c +actionBracket a b c = do + v <- Action ask + Action $ lift $ bracket a b (\x -> runReaderT (fromAction (c x)) v) + +actionFinally :: Action a -> IO b -> Action a +actionFinally a b = do + v <- Action ask + Action $ lift $ finally (runReaderT (fromAction a) v) b + +apply1 :: (RuleResult key ~ value, ShakeValue key, Typeable value) => key -> Action value +apply1 k = head <$> apply [k] + +apply :: (RuleResult key ~ value, ShakeValue key, Typeable value) => [key] -> Action [value] +apply ks = do + db <- Action $ asks actionDatabase + (is, vs) <- liftIO $ build db ks + ref <- Action $ asks actionDeps + liftIO $ modifyIORef ref (ResultDeps is <>) + pure vs + +runActions :: Database -> [Action a] -> IO [a] +runActions db xs = do + deps <- newIORef mempty + runReaderT (fromAction $ parallel xs) $ SAction db deps + +-- | Returns the set of dirty keys annotated with their age (in # of builds) +getDirtySet :: Action [(Key, Int)] +getDirtySet = do + db <- getDatabase + liftIO $ Development.IDE.Graph.Internal.Database.getDirtySet db + +getKeysAndVisitedAge :: Action [(Key, Int)] +getKeysAndVisitedAge = do + db <- getDatabase + liftIO $ Development.IDE.Graph.Internal.Database.getKeysAndVisitAge db
src/Development/IDE/Graph/Internal/Database.hs view
@@ -4,80 +4,83 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} module Development.IDE.Graph.Internal.Database (newDatabase, incDatabase, build, getDirtySet, getKeysAndVisitAge) where import Control.Concurrent.Async import Control.Concurrent.Extra+import Control.Concurrent.STM.Stats (STM, atomically,+ atomicallyNamed,+ modifyTVar', newTVarIO,+ readTVarIO) import Control.Exception import Control.Monad-import Control.Monad.IO.Class (MonadIO (liftIO))-import Control.Monad.Trans.Class (lift)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Reader-import qualified Control.Monad.Trans.State.Strict as State+import qualified Control.Monad.Trans.State.Strict as State import Data.Dynamic import Data.Either-import Data.Foldable (traverse_)+import Data.Foldable (for_, traverse_)+import Data.HashSet (HashSet)+import qualified Data.HashSet as HSet import Data.IORef.Extra-import Data.IntSet (IntSet)-import qualified Data.IntSet as Set import Data.Maybe+import Data.Traversable (for) import Data.Tuple.Extra import Development.IDE.Graph.Classes-import qualified Development.IDE.Graph.Internal.Ids as Ids-import Development.IDE.Graph.Internal.Intern-import qualified Development.IDE.Graph.Internal.Intern as Intern import Development.IDE.Graph.Internal.Rules import Development.IDE.Graph.Internal.Types+import qualified Focus+import qualified ListT+import qualified StmContainers.Map as SMap import System.IO.Unsafe-import System.Time.Extra (duration)+import System.Time.Extra (duration) newDatabase :: Dynamic -> TheRules -> IO Database newDatabase databaseExtra databaseRules = do- databaseStep <- newIORef $ Step 0- databaseLock <- newLock- databaseIds <- newIORef Intern.empty- databaseValues <- Ids.empty- databaseReverseDeps <- Ids.empty- databaseReverseDepsLock <- newLock+ databaseStep <- newTVarIO $ Step 0+ databaseValues <- atomically SMap.new pure Database{..} --- | Increment the step and mark dirty+-- | Increment the step and mark dirty.+-- Assumes that the database is not running a build incDatabase :: Database -> Maybe [Key] -> IO ()--- all keys are dirty-incDatabase db Nothing = do- modifyIORef' (databaseStep db) $ \(Step i) -> Step $ i + 1- withLock (databaseLock db) $- Ids.forMutate (databaseValues db) $ \_ -> second $ \case- Clean x -> Dirty (Just x)- Dirty x -> Dirty x- Running _ _ x -> Dirty x -- only some keys are dirty incDatabase db (Just kk) = do- modifyIORef' (databaseStep db) $ \(Step i) -> Step $ i + 1- intern <- readIORef (databaseIds db)- let dirtyIds = mapMaybe (`Intern.lookup` intern) kk- transitiveDirtyIds <- transitiveDirtySet db dirtyIds- withLock (databaseLock db) $- Ids.forMutate (databaseValues db) $ \i -> \case- (k, Running _ _ x) -> (k, Dirty x)- (k, Clean x) | i `Set.member` transitiveDirtyIds ->- (k, Dirty (Just x))- other -> other+ atomicallyNamed "incDatabase" $ modifyTVar' (databaseStep db) $ \(Step i) -> Step $ i + 1+ transitiveDirtyKeys <- transitiveDirtySet db kk+ for_ transitiveDirtyKeys $ \k ->+ -- Updating all the keys atomically is not necessary+ -- since we assume that no build is mutating the db.+ -- Therefore run one transaction per key to minimise contention.+ atomicallyNamed "incDatabase" $ SMap.focus updateDirty k (databaseValues db) +-- all keys are dirty+incDatabase db Nothing = do+ atomically $ modifyTVar' (databaseStep db) $ \(Step i) -> Step $ i + 1+ let list = SMap.listT (databaseValues db)+ atomicallyNamed "incDatabase - all " $ flip ListT.traverse_ list $ \(k,_) ->+ SMap.focus updateDirty k (databaseValues db) +updateDirty :: Monad m => Focus.Focus KeyDetails m ()+updateDirty = Focus.adjust $ \(KeyDetails status rdeps) ->+ let status'+ | Running _ _ _ x <- status = Dirty x+ | Clean x <- status = Dirty (Just x)+ | otherwise = status+ in KeyDetails status' rdeps -- | Unwrap and build a list of keys in parallel build :: forall key value . (RuleResult key ~ value, Typeable key, Show key, Hashable key, Eq key, Typeable value)- => Database -> [key] -> IO ([Id], [value])+ => Database -> [key] -> IO ([Key], [value]) build db keys = do- (ids, vs) <- runAIO $ fmap unzip $ either return liftIO =<< builder db (map (Right . Key) keys)+ (ids, vs) <- runAIO $ fmap unzip $ either return liftIO =<<+ builder db (map Key keys) pure (ids, map (asV . resultValue) vs) where asV :: Value -> value@@ -87,80 +90,70 @@ -- If none of the keys are dirty, we can return the results immediately. -- Otherwise, a blocking computation is returned *which must be evaluated asynchronously* to avoid deadlock. builder- :: Database -> [Either Id Key] -> AIO (Either [(Id, Result)] (IO [(Id, Result)]))-builder db@Database{..} keys = do- -- Things that I need to force before my results are ready- toForce <- liftIO $ newIORef []-- results <- withLockAIO databaseLock $ do- flip traverse keys $ \idKey -> do- -- Resolve the id- id <- case idKey of- Left id -> pure id- Right key -> liftIO $ do- ids <- readIORef databaseIds- case Intern.lookup key ids of- Just v -> pure v- Nothing -> do- (ids, id) <- pure $ Intern.add key ids- writeIORef' databaseIds ids- return id-- -- Spawn the id if needed- status <- liftIO $ Ids.lookup databaseValues id- val <- case fromMaybe (fromRight undefined idKey, Dirty Nothing) status of- (_, Clean r) -> pure r- (_, Running force val _) -> do- liftIO $ modifyIORef toForce (Wait force :)- pure val- (key, Dirty s) -> do- act <- unliftAIO (refresh db key id s)- let (force, val) = splitIO (join act)- liftIO $ Ids.insert databaseValues id (key, Running force val s)- liftIO $ modifyIORef toForce (Spawn force:)- pure val+ :: Database -> [Key] -> AIO (Either [(Key, Result)] (IO [(Key, Result)]))+builder db@Database{..} keys = withRunInIO $ \(RunInIO run) -> do+ -- Things that I need to force before my results are ready+ toForce <- liftIO $ newTVarIO []+ current <- liftIO $ readTVarIO databaseStep+ results <- liftIO $ for keys $ \id ->+ -- Updating the status of all the dependencies atomically is not necessary.+ -- Therefore, run one transaction per dep. to avoid contention+ atomicallyNamed "builder" $ do+ -- Spawn the id if needed+ status <- SMap.lookup id databaseValues+ val <- case viewDirty current $ maybe (Dirty Nothing) keyStatus status of+ Clean r -> pure r+ Running _ force val _ -> do+ modifyTVar' toForce (Wait force :)+ pure val+ Dirty s -> do+ let act = run (refresh db id s)+ (force, val) = splitIO (join act)+ SMap.focus (updateStatus $ Running current force val s) id databaseValues+ modifyTVar' toForce (Spawn force:)+ pure val - pure (id, val)+ pure (id, val) - toForceList <- liftIO $ readIORef toForce- waitAll <- unliftAIO $ mapConcurrentlyAIO_ id toForceList- case toForceList of- [] -> return $ Left results- _ -> return $ Right $ do- waitAll- pure results+ toForceList <- liftIO $ readTVarIO toForce+ let waitAll = run $ mapConcurrentlyAIO_ id toForceList+ case toForceList of+ [] -> return $ Left results+ _ -> return $ Right $ do+ waitAll+ pure results -- | Refresh a key: -- * If no dirty dependencies and we have evaluated the key previously, then we refresh it in the current thread. -- This assumes that the implementation will be a lookup -- * Otherwise, we spawn a new thread to refresh the dirty deps (if any) and the key itself-refresh :: Database -> Key -> Id -> Maybe Result -> AIO (IO Result)-refresh db key id result@(Just me@Result{resultDeps = ResultDeps deps}) = do- res <- builder db $ map Left deps+refresh :: Database -> Key -> Maybe Result -> AIO (IO Result)+refresh db key result@(Just me@Result{resultDeps = ResultDeps deps}) = do+ res <- builder db deps case res of Left res -> if isDirty res- then asyncWithCleanUp $ liftIO $ compute db key id RunDependenciesChanged result- else pure $ compute db key id RunDependenciesSame result+ then asyncWithCleanUp $ liftIO $ compute db key RunDependenciesChanged result+ else pure $ compute db key RunDependenciesSame result Right iores -> asyncWithCleanUp $ liftIO $ do res <- iores let mode = if isDirty res then RunDependenciesChanged else RunDependenciesSame- compute db key id mode result+ compute db key mode result where isDirty = any (\(_,dep) -> resultBuilt me < resultChanged dep) -refresh db key id result =- asyncWithCleanUp $ liftIO $ compute db key id RunDependenciesChanged result+refresh db key result =+ asyncWithCleanUp $ liftIO $ compute db key RunDependenciesChanged result -- | Compute a key.-compute :: Database -> Key -> Id -> RunMode -> Maybe Result -> IO Result-compute db@Database{..} key id mode result = do+compute :: Database -> Key -> RunMode -> Maybe Result -> IO Result+compute db@Database{..} key mode result = do let act = runRule databaseRules key (fmap resultData result) mode deps <- newIORef UnknownDeps (execution, RunResult{..}) <- duration $ runReaderT (fromAction act) $ SAction db deps- built <- readIORef databaseStep+ built <- readTVarIO databaseStep deps <- readIORef deps let changed = if runChanged == ChangedRecomputeDiff then built else maybe built resultChanged result built' = if runChanged /= ChangedNothing then built else changed@@ -173,28 +166,34 @@ && runChanged /= ChangedNothing -> do void $ forkIO $- updateReverseDeps id db (getResultDepsDefault [] previousDeps) (Set.fromList deps)+ updateReverseDeps key db+ (getResultDepsDefault [] previousDeps)+ (HSet.fromList deps) _ -> pure ()- withLock databaseLock $- Ids.insert databaseValues id (key, Clean res)+ atomicallyNamed "compute" $ SMap.focus (updateStatus $ Clean res) key databaseValues pure res +updateStatus :: Monad m => Status -> Focus.Focus KeyDetails m ()+updateStatus res = Focus.alter+ (Just . maybe (KeyDetails res mempty)+ (\it -> it{keyStatus = res}))+ -- | Returns the set of dirty keys annotated with their age (in # of builds)-getDirtySet :: Database -> IO [(Id,(Key, Int))]+getDirtySet :: Database -> IO [(Key, Int)] getDirtySet db = do- Step curr <- readIORef (databaseStep db)- dbContents <- Ids.toList (databaseValues db)+ Step curr <- readTVarIO (databaseStep db)+ dbContents <- getDatabaseValues db let calcAge Result{resultBuilt = Step x} = curr - x calcAgeStatus (Dirty x)=calcAge <$> x calcAgeStatus _ = Nothing- return $ mapMaybe ((secondM.secondM) calcAgeStatus) dbContents+ return $ mapMaybe (secondM calcAgeStatus) dbContents -- | Returns ann approximation of the database keys, -- annotated with how long ago (in # builds) they were visited getKeysAndVisitAge :: Database -> IO [(Key, Int)] getKeysAndVisitAge db = do- values <- Ids.elems (databaseValues db)- Step curr <- readIORef (databaseStep db)+ values <- getDatabaseValues db+ Step curr <- readTVarIO (databaseStep db) let keysWithVisitAge = mapMaybe (secondM (fmap getAge . getResult)) values getAge Result{resultVisited = Step s} = curr - s return keysWithVisitAge@@ -215,34 +214,38 @@ -- | Update the reverse dependencies of an Id updateReverseDeps- :: Id -- ^ Id+ :: Key -- ^ Id -> Database- -> [Id] -- ^ Previous direct dependencies of Id- -> IntSet -- ^ Current direct dependencies of Id+ -> [Key] -- ^ Previous direct dependencies of Id+ -> HashSet Key -- ^ Current direct dependencies of Id -> IO ()-updateReverseDeps myId db prev new = withLock (databaseReverseDepsLock db) $ uninterruptibleMask_ $ do+updateReverseDeps myId db prev new = uninterruptibleMask_ $ do forM_ prev $ \d ->- unless (d `Set.member` new) $- doOne (Set.delete myId) d- forM_ (Set.elems new) $- doOne (Set.insert myId)+ unless (d `HSet.member` new) $+ doOne (HSet.delete myId) d+ forM_ (HSet.toList new) $+ doOne (HSet.insert myId) where- doOne f id = do- rdeps <- getReverseDependencies db id- Ids.insert (databaseReverseDeps db) id (f $ fromMaybe mempty rdeps)+ alterRDeps f =+ Focus.adjust (onKeyReverseDeps f)+ -- updating all the reverse deps atomically is not needed.+ -- Therefore, run individual transactions for each update+ -- in order to avoid contention+ doOne f id = atomicallyNamed "updateReverseDeps" $+ SMap.focus (alterRDeps f) id (databaseValues db) -getReverseDependencies :: Database -> Id -> IO (Maybe (IntSet))-getReverseDependencies db = Ids.lookup (databaseReverseDeps db)+getReverseDependencies :: Database -> Key -> STM (Maybe (HashSet Key))+getReverseDependencies db = (fmap.fmap) keyReverseDeps . flip SMap.lookup (databaseValues db) -transitiveDirtySet :: Foldable t => Database -> t Id -> IO IntSet-transitiveDirtySet database = flip State.execStateT Set.empty . traverse_ loop+transitiveDirtySet :: Foldable t => Database -> t Key -> IO (HashSet Key)+transitiveDirtySet database = flip State.execStateT HSet.empty . traverse_ loop where loop x = do seen <- State.get- if x `Set.member` seen then pure () else do- State.put (Set.insert x seen)- next <- lift $ getReverseDependencies database x- traverse_ loop (maybe mempty Set.toList next)+ if x `HSet.member` seen then pure () else do+ State.put (HSet.insert x seen)+ next <- lift $ atomically $ getReverseDependencies database x+ traverse_ loop (maybe mempty HSet.toList next) -- | IO extended to track created asyncs to clean them up when the thread is killed, -- generalizing 'withAsync'@@ -263,15 +266,17 @@ atomicModifyIORef'_ st (void a :) return $ wait a -withLockAIO :: Lock -> AIO a -> AIO a-withLockAIO lock act = do- io <- unliftAIO act- liftIO $ withLock lock io- unliftAIO :: AIO a -> AIO (IO a) unliftAIO act = do st <- AIO ask return $ runReaderT (unAIO act) st++newtype RunInIO = RunInIO (forall a. AIO a -> IO a)++withRunInIO :: (RunInIO -> AIO b) -> AIO b+withRunInIO k = do+ st <- AIO ask+ k $ RunInIO (\aio -> runReaderT (unAIO aio) st) cleanupAsync :: IORef [Async a] -> IO () cleanupAsync ref = uninterruptibleMask_ $ do
− src/Development/IDE/Graph/Internal/Ids.hs
@@ -1,160 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE UnboxedTuples #-}---- Note that argument order is more like IORef than Map, because its mutable-module Development.IDE.Graph.Internal.Ids(- Ids, Id,- empty, insert, lookup, fromList,- null, size, sizeUpperBound,- forWithKeyM_, forCopy, forMutate,- toList, elems, toMap- ) where--import Control.Exception-import Control.Monad.Extra-import Data.Functor-import qualified Data.HashMap.Strict as Map-import Data.IORef.Extra-import Data.List.Extra (zipFrom)-import Data.Maybe-import Data.Primitive.Array hiding (fromList)-import Development.IDE.Graph.Internal.Intern (Id)-import GHC.Exts (RealWorld)-import GHC.IO (IO (..))-import Prelude hiding (lookup, null)---newtype Ids a = Ids (IORef (S a))--data S a = S- {capacity :: {-# UNPACK #-} !Int -- ^ Number of entries in values, initially 0- ,used :: {-# UNPACK #-} !Int -- ^ Capacity that has been used, assuming no gaps from index 0, initially 0- ,values :: {-# UNPACK #-} !(MutableArray RealWorld (Maybe a))- }---empty :: IO (Ids a)-empty = do- let capacity = 0- let used = 0- values <- newArray capacity Nothing- Ids <$> newIORef S{..}--fromList :: [a] -> IO (Ids a)-fromList xs = do- let capacity = length xs- let used = capacity- values <- newArray capacity Nothing- forM_ (zipFrom 0 xs) $ \(i, x) ->- writeArray values i $ Just x- Ids <$> newIORef S{..}--sizeUpperBound :: Ids a -> IO Int-sizeUpperBound (Ids ref) = do- S{..} <- readIORef ref- pure used---size :: Ids a -> IO Int-size (Ids ref) = do- S{..} <- readIORef ref- let go !acc i- | i < 0 = pure acc- | otherwise = do- v <- readArray values i- if isJust v then go (acc+1) (i-1) else go acc (i-1)- go 0 (used-1)---toMap :: Ids a -> IO (Map.HashMap Id a)-toMap ids = do- mp <- Map.fromList <$> toListUnsafe ids- pure $! mp--forWithKeyM_ :: Ids a -> (Id -> a -> IO ()) -> IO ()-forWithKeyM_ (Ids ref) f = do- S{..} <- readIORef ref- let go !i | i >= used = pure ()- | otherwise = do- v <- readArray values i- whenJust v $ f $ fromIntegral i- go $ i+1- go 0--forCopy :: Ids a -> (a -> b) -> IO (Ids b)-forCopy (Ids ref) f = do- S{..} <- readIORef ref- values2 <- newArray capacity Nothing- let go !i | i >= used = pure ()- | otherwise = do- v <- readArray values i- whenJust v $ \v -> writeArray values2 i $ Just $ f v- go $ i+1- go 0- Ids <$> newIORef (S capacity used values2)---forMutate :: Ids a -> (Id -> a -> a) -> IO ()-forMutate (Ids ref) f = do- S{..} <- readIORef ref- let go !i | i >= used = pure ()- | otherwise = do- v <- readArray values i- whenJust v $ \v -> writeArray values i $! Just $! f i v- go $ i+1- go 0---toListUnsafe :: Ids a -> IO [(Id, a)]-toListUnsafe (Ids ref) = do- S{..} <- readIORef ref-- -- execute in O(1) stack- -- see https://neilmitchell.blogspot.co.uk/2015/09/making-sequencemapm-for-io-take-o1-stack.html- let index _ i | i >= used = []- index r i | IO io <- readArray values i = case io r of- (# r, Nothing #) -> index r (i+1)- (# r, Just v #) -> (fromIntegral i, v) : index r (i+1)-- IO $ \r -> (# r, index r 0 #)---toList :: Ids a -> IO [(Id, a)]-toList ids = do- xs <- toListUnsafe ids- let demand (_:xs) = demand xs- demand [] = ()- evaluate $ demand xs- pure xs--elems :: Ids a -> IO [a]-elems ids = map snd <$> toList ids--null :: Ids a -> IO Bool-null ids = (== 0) <$> sizeUpperBound ids---insert :: Ids a -> Id -> a -> IO ()-insert (Ids ref) (i) v = do- S{..} <- readIORef ref- let ii = fromIntegral i- if ii < capacity then do- writeArray values ii $ Just v- when (ii >= used) $ writeIORef' ref S{used=ii+1,..}- else do- c2<- pure $ max (capacity * 2) (ii + 10000)- v2 <- newArray c2 Nothing- copyMutableArray v2 0 values 0 capacity- writeArray v2 ii $ Just v- writeIORef' ref $ S c2 (ii+1) v2--lookup :: Ids a -> Id -> IO (Maybe a)-lookup (Ids ref) (i) = do- S{..} <- readIORef ref- let ii = fromIntegral i- if ii < used then- readArray values ii- else- pure Nothing
− src/Development/IDE/Graph/Internal/Intern.hs
@@ -1,41 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Development.IDE.Graph.Internal.Intern(- Intern, Id,- empty, insert, add, lookup, toList, fromList- ) where--import qualified Data.HashMap.Strict as Map-import Data.List (foldl')-import Development.IDE.Graph.Classes-import Prelude hiding (lookup)----- Invariant: The first field is the highest value in the Map-data Intern a = Intern {-# UNPACK #-} !Int !(Map.HashMap a Id)--type Id = Int--empty :: Intern a-empty = Intern 0 Map.empty---insert :: (Eq a, Hashable a) => a -> Id -> Intern a -> Intern a-insert k v (Intern n mp) = Intern (max n v) $ Map.insert k v mp---add :: (Eq a, Hashable a) => a -> Intern a -> (Intern a, Id)-add k (Intern v mp) = (Intern v2 $ Map.insert k v2 mp, v2)- where v2 = v + 1---lookup :: (Eq a, Hashable a) => a -> Intern a -> Maybe Id-lookup k (Intern _ mp) = Map.lookup k mp---toList :: Intern a -> [(a, Id)]-toList (Intern _ mp) = Map.toList mp---fromList :: (Eq a, Hashable a) => [(a, Id)] -> Intern a-fromList xs = Intern (foldl' max 0 [i | (_, i) <- xs]) (Map.fromList xs)
src/Development/IDE/Graph/Internal/Options.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE RecordWildCards #-}- module Development.IDE.Graph.Internal.Options where import Control.Monad.Trans.Reader
src/Development/IDE/Graph/Internal/Paths.hs view
@@ -20,8 +20,14 @@ htmlDataFiles :: [(FilePath, BS.ByteString)] htmlDataFiles =- [ ("profile.html", $(embedFile "html/profile.html"))+ [+#ifdef __GHCIDE__+ ("profile.html", $(embedFile "hls-graph/html/profile.html"))+ , ("shake.js", $(embedFile "hls-graph/html/shake.js"))+#else+ ("profile.html", $(embedFile "html/profile.html")) , ("shake.js", $(embedFile "html/shake.js"))+#endif ] readDataFileHTML :: FilePath -> IO LBS.ByteString
src/Development/IDE/Graph/Internal/Profile.hs view
@@ -7,15 +7,14 @@ module Development.IDE.Graph.Internal.Profile (writeProfile) where +import Control.Concurrent.STM.Stats (readTVarIO) import Data.Bifunctor import qualified Data.ByteString.Lazy.Char8 as LBS import Data.Char import Data.Dynamic (toDyn)+import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as Map-import Data.IORef-import Data.IntMap (IntMap)-import qualified Data.IntMap as IntMap-import qualified Data.IntSet as Set+import qualified Data.HashSet as Set import Data.List (dropWhileEnd, foldl', intercalate, partition, sort,@@ -28,7 +27,6 @@ iso8601DateFormat) import Development.IDE.Graph.Classes import Development.IDE.Graph.Internal.Database (getDirtySet)-import qualified Development.IDE.Graph.Internal.Ids as Ids import Development.IDE.Graph.Internal.Paths import Development.IDE.Graph.Internal.Types import qualified Language.Javascript.DGTable as DGTable@@ -50,7 +48,7 @@ (report, mapping) <- toReport db dirtyKeysMapped <- do dirtyIds <- Set.fromList . fmap fst <$> getDirtySet db- let dirtyKeysMapped = mapMaybe (`IntMap.lookup` mapping) . Set.toList $ dirtyIds+ let dirtyKeysMapped = mapMaybe (`Map.lookup` mapping) . Set.toList $ dirtyIds return $ Just $ sort dirtyKeysMapped rpt <- generateHTML dirtyKeysMapped report LBS.writeFile out rpt@@ -60,12 +58,12 @@ -- | Eliminate all errors from the database, pretending they don't exist -- resultsOnly :: Map.HashMap Id (Key, Status) -> Map.HashMap Id (Key, Result (Either BS.ByteString Value))-resultsOnly :: [(Ids.Id, (k, Status))] -> Map.HashMap Ids.Id (k, Result)-resultsOnly mp = Map.map (fmap (\r ->+resultsOnly :: [(Key, Status)] -> Map.HashMap Key Result+resultsOnly mp = Map.map (\r -> r{resultDeps = mapResultDeps (filter (isJust . flip Map.lookup keep)) $ resultDeps r}- )) keep+ ) keep where- keep = Map.fromList $ mapMaybe ((traverse.traverse) getResult) mp+ keep = Map.fromList $ mapMaybe (traverse getResult) mp -- | Given a map of representing a dependency order (with a show for error messages), find an ordering for the items such -- that no item points to an item before itself.@@ -102,36 +100,35 @@ Nothing -> g (free, mp) (k, ds) Just todo -> (free, Map.insert d (Just $ (k,ds) : todo) mp) -prepareForDependencyOrder :: Database -> IO (Map.HashMap Ids.Id (Key, Result))+prepareForDependencyOrder :: Database -> IO (HashMap Key Result) prepareForDependencyOrder db = do- current <- readIORef $ databaseStep db- Map.insert (-1) (Key "alwaysRerun", alwaysRerunResult current) . resultsOnly- <$> Ids.toList (databaseValues db)+ current <- readTVarIO $ databaseStep db+ Map.insert (Key "alwaysRerun") (alwaysRerunResult current) . resultsOnly+ <$> getDatabaseValues db -- | Returns a list of profile entries, and a mapping linking a non-error Id to its profile entry-toReport :: Database -> IO ([ProfileEntry], IntMap Int)+toReport :: Database -> IO ([ProfileEntry], HashMap Key Int) toReport db = do status <- prepareForDependencyOrder db- let order = let shw i = maybe "<unknown>" (show . fst) $ Map.lookup i status- in dependencyOrder shw- $ map (second (getResultDepsDefault [-1] . resultDeps . snd))+ let order = dependencyOrder show+ $ map (second (getResultDepsDefault [Key "alwaysRerun"] . resultDeps)) $ Map.toList status- ids = IntMap.fromList $ zip order [0..]+ ids = Map.fromList $ zip order [0..] - steps = let xs = nubOrd $ concat [[resultChanged, resultBuilt, resultVisited] | (_k, Result{..}) <- Map.elems status]+ steps = let xs = nubOrd $ concat [[resultChanged, resultBuilt, resultVisited] | Result{..} <- Map.elems status] in Map.fromList $ zip (sortBy (flip compare) xs) [0..] - f (k, Result{..}) = ProfileEntry+ f k Result{..} = ProfileEntry {prfName = show k ,prfBuilt = fromStep resultBuilt ,prfVisited = fromStep resultVisited ,prfChanged = fromStep resultChanged- ,prfDepends = map pure $ mapMaybe (`IntMap.lookup` ids) $ getResultDepsDefault [-1] resultDeps+ ,prfDepends = map pure $ mapMaybe (`Map.lookup` ids) $ getResultDepsDefault [Key "alwaysRerun"] resultDeps ,prfExecution = resultExecution } where fromStep i = fromJust $ Map.lookup i steps- pure ([maybe (error "toReport") f $ Map.lookup i status | i <- order], ids)+ pure ([maybe (error "toReport") (f i) $ Map.lookup i status | i <- order], ids) alwaysRerunResult :: Step -> Result alwaysRerunResult current = Result (Value $ toDyn "<alwaysRerun>") (Step 0) (Step 0) current (ResultDeps []) 0 mempty@@ -144,7 +141,7 @@ f other = error other runTemplate f report -generateJSONBuild :: Maybe [Ids.Id] -> String+generateJSONBuild :: Maybe [Int] -> String generateJSONBuild (Just dirtyKeys) = jsonList [jsonList (map show dirtyKeys)] generateJSONBuild Nothing = jsonList []
src/Development/IDE/Graph/Internal/Rules.hs view
@@ -1,60 +1,58 @@--- We deliberately want to ensure the function we add to the rule database--- has the constraints we need on it when we get it out.-{-# OPTIONS_GHC -Wno-redundant-constraints #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE RecordWildCards #-}--module Development.IDE.Graph.Internal.Rules where--import Development.IDE.Graph.Classes-import Control.Exception.Extra-import Control.Monad-import Control.Monad.IO.Class-import qualified Data.ByteString as BS-import Data.Dynamic-import Data.Typeable-import Data.IORef-import qualified Data.HashMap.Strict as Map-import Control.Monad.Trans.Reader-import Development.IDE.Graph.Internal.Types-import Data.Maybe---- | The type mapping between the @key@ or a rule and the resulting @value@.--- See 'addBuiltinRule' and 'Development.Shake.Rule.apply'.-type family RuleResult key -- = value--action :: Action a -> Rules ()-action x = do- ref <- Rules $ asks rulesActions- liftIO $ modifyIORef' ref (void x:)--addRule- :: forall key value .- (RuleResult key ~ value, Typeable key, Hashable key, Eq key, Typeable value)- => (key -> Maybe BS.ByteString -> RunMode -> Action (RunResult value))- -> Rules ()-addRule f = do- ref <- Rules $ asks rulesMap- liftIO $ modifyIORef' ref $ Map.insert (typeRep (Proxy :: Proxy key)) (toDyn f2)- where- f2 :: Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value)- f2 (Key a) b c = do- v <- f (fromJust $ cast a :: key) b c- v <- liftIO $ evaluate v- pure $ (Value . toDyn) <$> v--runRule- :: TheRules -> Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value)-runRule rules key@(Key t) bs mode = case Map.lookup (typeOf t) rules of- Nothing -> liftIO $ errorIO "Could not find key"- Just x -> unwrapDynamic x key bs mode--runRules :: Dynamic -> Rules () -> IO (TheRules, [Action ()])-runRules rulesExtra (Rules rules) = do- rulesActions <- newIORef []- rulesMap <- newIORef Map.empty- runReaderT rules SRules{..}- (,) <$> readIORef rulesMap <*> readIORef rulesActions+-- We deliberately want to ensure the function we add to the rule database +-- has the constraints we need on it when we get it out. +{-# OPTIONS_GHC -Wno-redundant-constraints #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} + +module Development.IDE.Graph.Internal.Rules where + +import Control.Exception.Extra +import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Trans.Reader +import qualified Data.ByteString as BS +import Data.Dynamic +import qualified Data.HashMap.Strict as Map +import Data.IORef +import Data.Maybe +import Data.Typeable +import Development.IDE.Graph.Classes +import Development.IDE.Graph.Internal.Types + +-- | The type mapping between the @key@ or a rule and the resulting @value@. +-- See 'addBuiltinRule' and 'Development.Shake.Rule.apply'. +type family RuleResult key -- = value + +action :: Action a -> Rules () +action x = do + ref <- Rules $ asks rulesActions + liftIO $ modifyIORef' ref (void x:) + +addRule + :: forall key value . + (RuleResult key ~ value, Typeable key, Hashable key, Eq key, Typeable value) + => (key -> Maybe BS.ByteString -> RunMode -> Action (RunResult value)) + -> Rules () +addRule f = do + ref <- Rules $ asks rulesMap + liftIO $ modifyIORef' ref $ Map.insert (typeRep (Proxy :: Proxy key)) (toDyn f2) + where + f2 :: Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value) + f2 (Key a) b c = do + v <- f (fromJust $ cast a :: key) b c + v <- liftIO $ evaluate v + pure $ Value . toDyn <$> v + +runRule + :: TheRules -> Key -> Maybe BS.ByteString -> RunMode -> Action (RunResult Value) +runRule rules key@(Key t) bs mode = case Map.lookup (typeOf t) rules of + Nothing -> liftIO $ errorIO "Could not find key" + Just x -> unwrapDynamic x key bs mode + +runRules :: Dynamic -> Rules () -> IO (TheRules, [Action ()]) +runRules rulesExtra (Rules rules) = do + rulesActions <- newIORef [] + rulesMap <- newIORef Map.empty + runReaderT rules SRules{..} + (,) <$> readIORef rulesMap <*> readIORef rulesActions
src/Development/IDE/Graph/Internal/Types.hs view
@@ -1,35 +1,41 @@--+{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} module Development.IDE.Graph.Internal.Types where import Control.Applicative-import Control.Concurrent.Extra import Control.Monad.Catch+#if __GLASGOW_HASKELL__ < 808 -- Needed in GHC 8.6.5+import Control.Concurrent.STM.Stats (TVar, atomically) import Control.Monad.Fail+#else+import GHC.Conc (TVar, atomically)+#endif import Control.Monad.IO.Class import Control.Monad.Trans.Reader-import Data.Aeson (FromJSON, ToJSON)-import qualified Data.ByteString as BS+import Data.Aeson (FromJSON, ToJSON)+import Data.Bifunctor (second)+import qualified Data.ByteString as BS import Data.Dynamic-import qualified Data.HashMap.Strict as Map+import qualified Data.HashMap.Strict as Map+import Data.HashSet (HashSet) import Data.IORef-import Data.IntSet (IntSet) import Data.Maybe import Data.Typeable import Development.IDE.Graph.Classes-import Development.IDE.Graph.Internal.Ids-import Development.IDE.Graph.Internal.Intern-import GHC.Generics (Generic)-import System.Time.Extra (Seconds)+import GHC.Generics (Generic)+import qualified ListT+import StmContainers.Map (Map)+import qualified StmContainers.Map as SMap+import System.Time.Extra (Seconds) unwrapDynamic :: forall a . Typeable a => Dynamic -> a@@ -85,27 +91,47 @@ newtype Value = Value Dynamic +data KeyDetails = KeyDetails {+ keyStatus :: !Status,+ keyReverseDeps :: !(HashSet Key)+ }++onKeyReverseDeps :: (HashSet Key -> HashSet Key) -> KeyDetails -> KeyDetails+onKeyReverseDeps f it@KeyDetails{..} =+ it{keyReverseDeps = f keyReverseDeps}+ data Database = Database {- databaseExtra :: Dynamic,- databaseRules :: TheRules,- databaseStep :: !(IORef Step),- -- Hold the lock while mutating Ids/Values- databaseLock :: !Lock,- databaseIds :: !(IORef (Intern Key)),- databaseValues :: !(Ids (Key, Status)),- databaseReverseDeps :: !(Ids IntSet),- databaseReverseDepsLock :: !Lock+ databaseExtra :: Dynamic,+ databaseRules :: TheRules,+ databaseStep :: !(TVar Step),+ databaseValues :: !(Map Key KeyDetails) } +getDatabaseValues :: Database -> IO [(Key, Status)]+getDatabaseValues = atomically+ . (fmap.fmap) (second keyStatus)+ . ListT.toList+ . SMap.listT+ . databaseValues+ data Status = Clean Result | Dirty (Maybe Result)- | Running (IO ()) Result (Maybe Result)+ | Running {+ runningStep :: !Step,+ runningWait :: !(IO ()),+ runningResult :: Result,+ runningPrev :: !(Maybe Result)+ } +viewDirty :: Step -> Status -> Status+viewDirty currentStep (Running s _ _ re) | currentStep /= s = Dirty re+viewDirty _ other = other+ getResult :: Status -> Maybe Result-getResult (Clean re) = Just re-getResult (Dirty m_re) = m_re-getResult (Running _ _ m_re) = m_re+getResult (Clean re) = Just re+getResult (Dirty m_re) = m_re+getResult (Running _ _ _ m_re) = m_re -- watch out: this returns the previous result data Result = Result { resultValue :: !Value,@@ -117,14 +143,14 @@ resultData :: BS.ByteString } -data ResultDeps = UnknownDeps | AlwaysRerunDeps ![Id] | ResultDeps ![Id]+data ResultDeps = UnknownDeps | AlwaysRerunDeps ![Key] | ResultDeps ![Key] -getResultDepsDefault :: [Id] -> ResultDeps -> [Id]+getResultDepsDefault :: [Key] -> ResultDeps -> [Key] getResultDepsDefault _ (ResultDeps ids) = ids getResultDepsDefault _ (AlwaysRerunDeps ids) = ids getResultDepsDefault def UnknownDeps = def -mapResultDeps :: ([Id] -> [Id]) -> ResultDeps -> ResultDeps+mapResultDeps :: ([Key] -> [Key]) -> ResultDeps -> ResultDeps mapResultDeps f (ResultDeps ids) = ResultDeps $ f ids mapResultDeps f (AlwaysRerunDeps ids) = AlwaysRerunDeps $ f ids mapResultDeps _ UnknownDeps = UnknownDeps