Skip to content

Commit e7bd3d4

Browse files
committed
run session loader and worker in sperate
1 parent f3eb580 commit e7bd3d4

File tree

3 files changed

+141
-37
lines changed

3 files changed

+141
-37
lines changed

ghcide/ghcide.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ library
110110
, unliftio-core
111111
, unordered-containers >=0.2.10.0
112112
, vector
113+
, ListT
113114

114115
if os(windows)
115116
build-depends: Win32
@@ -204,6 +205,7 @@ library
204205
Development.IDE.GHC.CPP
205206
Development.IDE.GHC.Warnings
206207
Development.IDE.Types.Action
208+
Development.IDE.Session.OrderedSet
207209

208210
if flag(pedantic)
209211
ghc-options:

ghcide/session-loader/Development/IDE/Session.hs

+100-37
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Control.Exception.Safe as Safe
2525
import Control.Monad
2626
import Control.Monad.Extra as Extra
2727
import Control.Monad.IO.Class
28+
import Control.Monad.Trans.Maybe (MaybeT (MaybeT, runMaybeT))
2829
import qualified Crypto.Hash.SHA1 as H
2930
import Data.Aeson hiding (Error)
3031
import Data.Bifunctor
@@ -103,8 +104,7 @@ import qualified Data.HashSet as Set
103104
import qualified Data.Set as OS
104105
import Database.SQLite.Simple
105106
import Development.IDE.Core.Tracing (withTrace)
106-
import Development.IDE.Core.WorkerThread (awaitRunInThread,
107-
withWorkerQueue)
107+
import Development.IDE.Core.WorkerThread (withWorkerQueue)
108108
import qualified Development.IDE.GHC.Compat.Util as Compat
109109
import Development.IDE.Session.Diagnostics (renderCradleError)
110110
import Development.IDE.Types.Shake (WithHieDb,
@@ -119,12 +119,17 @@ import qualified System.Random as Random
119119
import System.Random (RandomGen)
120120
import Text.ParserCombinators.ReadP (readP_to_S)
121121

122+
import Control.Concurrent.STM (STM)
123+
import qualified Control.Monad.STM as STM
124+
import qualified Development.IDE.Session.OrderedSet as S
125+
import qualified Focus
122126
import GHC.Data.Bag
123127
import GHC.Driver.Env (hsc_all_home_unit_ids)
124128
import GHC.Driver.Errors.Types
125129
import GHC.Types.Error (errMsgDiagnostic,
126130
singleMessage)
127131
import GHC.Unit.State
132+
import qualified StmContainers.Map as STM
128133

129134
data Log
130135
= LogSettingInitialDynFlags
@@ -148,10 +153,14 @@ data Log
148153
| LogSessionLoadingChanged
149154
| LogSessionNewLoadedFiles ![FilePath]
150155
| LogSessionReloadOnError FilePath ![FilePath]
156+
| LogGetOptionsLoop !FilePath
157+
| LogGetSessionRetry !FilePath
151158
deriving instance Show Log
152159

153160
instance Pretty Log where
154161
pretty = \case
162+
LogGetSessionRetry path -> "Retrying get session for" <+> pretty path
163+
LogGetOptionsLoop fp -> "Loop: getOptions for" <+> pretty fp
155164
LogSessionReloadOnError path files ->
156165
"Reloading file due to error in" <+> pretty path <+> "with files:" <+> pretty files
157166
LogSessionNewLoadedFiles files ->
@@ -435,14 +444,14 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
435444
-- Mapping from hie.yaml file to HscEnv, one per hie.yaml file
436445
hscEnvs <- newVar Map.empty :: IO (Var HieMap)
437446
-- Mapping from a Filepath to HscEnv
438-
fileToFlags <- newVar Map.empty :: IO (Var FlagsMap)
447+
fileToFlags <- STM.newIO :: IO FlagsMap
439448
-- Mapping from a Filepath to its 'hie.yaml' location.
440449
-- Should hold the same Filepaths as 'fileToFlags', otherwise
441450
-- they are inconsistent. So, everywhere you modify 'fileToFlags',
442451
-- you have to modify 'filesMap' as well.
443-
filesMap <- newVar HM.empty :: IO (Var FilesMap)
452+
filesMap <- STM.newIO :: IO FilesMap
444453
-- Pending files waiting to be loaded
445-
pendingFilesTQueue <- newTQueueIO
454+
pendingFileSet <- S.newIO :: IO (S.OrderedSet FilePath)
446455
-- Version of the mappings above
447456
version <- newVar 0
448457
biosSessionLoadingVar <- newVar Nothing :: IO (Var (Maybe SessionLoadingPreferenceConfig))
@@ -559,7 +568,7 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
559568

560569

561570
let session :: (Maybe FilePath, NormalizedFilePath, ComponentOptions, FilePath)
562-
-> IO ((IdeResult HscEnvEq,[FilePath]), HashSet FilePath)
571+
-> IO ((IdeResult HscEnvEq,DependencyInfo), HashSet FilePath)
563572
session args@(hieYaml, _cfp, _opts, _libDir) = do
564573
(new_deps, old_deps) <- packageSetup args
565574

@@ -589,8 +598,11 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
589598
, "If you are using a .cabal file, please ensure that this module is listed in either the exposed-modules or other-modules section"
590599
]
591600

592-
void $ modifyVar' fileToFlags $ Map.insert hieYaml this_flags_map
593-
void $ modifyVar' filesMap $ flip HM.union (HM.fromList (map ((,hieYaml) . fst) $ concatMap toFlagsMap all_targets))
601+
let insertAll m xs = mapM_ (flip (uncurry STM.insert) m) xs
602+
atomically $ do
603+
STM.insert this_flags_map hieYaml fileToFlags
604+
insertAll filesMap $ map ((hieYaml,) . fst) $ concatMap toFlagsMap all_targets
605+
594606
-- Typecheck all files in the project on startup
595607
checkProject <- getCheckProject
596608
-- The VFS doesn't change on cradle edits, re-use the old one.
@@ -609,9 +621,9 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
609621
let !exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces
610622
liftIO $ atomically $ modifyTVar' (exportsMap shakeExtras) (exportsMap' <>)
611623
return [keys1, keys2]
612-
return $ (second Map.keys this_options, Set.fromList $ fromNormalizedFilePath <$> newLoaded)
624+
return $ (this_options, Set.fromList $ fromNormalizedFilePath <$> newLoaded)
613625

614-
let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq, [FilePath])
626+
let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq, DependencyInfo)
615627
consultCradle hieYaml cfp = do
616628
let lfpLog = makeRelative rootDir cfp
617629
logWith recorder Info $ LogCradlePath lfpLog
@@ -625,7 +637,7 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
625637
let progMsg = "Setting up " <> T.pack (takeBaseName (cradleRootDir cradle))
626638
<> " (for " <> T.pack lfpLog <> ")"
627639

628-
pendingFiles <- Set.insert cfp . Set.fromList <$> (atomically $ flushTQueue pendingFilesTQueue)
640+
pendingFiles <- Set.insert cfp . Set.fromList <$> (atomically $ S.toUnOrderedList pendingFileSet)
629641
errorFiles <- readIORef error_loading_files
630642
old_files <- readIORef cradle_files
631643
-- if the file is in error loading files, we fall back to single loading mode
@@ -652,18 +664,20 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
652664
((runTime, _):_)
653665
| compileTime == runTime -> do
654666
(results, allNewLoaded) <- session (hieYaml, toNormalizedFilePath' cfp, opts, libDir)
655-
-- put back to pending que if not listed in the results
656667
-- delete cfp even if we report No cradle target found for the cfp
657-
let remainPendingFiles = Set.delete cfp $ pendingFiles `Set.difference` allNewLoaded
658668
let newLoaded = pendingFiles `Set.intersection` allNewLoaded
659-
atomically $ forM_ remainPendingFiles (writeTQueue pendingFilesTQueue)
669+
-- delete all new loaded
670+
atomically $ forM_ allNewLoaded $ flip S.delete pendingFileSet
660671
-- log new loaded files
661672
logWith recorder Info $ LogSessionNewLoadedFiles $ Set.toList newLoaded
662673
-- remove all new loaded file from error loading files
663674
atomicModifyIORef' error_loading_files (\old -> (old `Set.difference` allNewLoaded, ()))
664675
atomicModifyIORef' cradle_files (\xs -> (newLoaded <> xs,()))
665676
return results
666-
| otherwise -> return (([renderPackageSetupException cfp GhcVersionMismatch{..}], Nothing),[])
677+
| otherwise -> do
678+
-- delete cfp from pending files
679+
atomically $ S.delete cfp pendingFileSet
680+
return (([renderPackageSetupException cfp GhcVersionMismatch{..}], Nothing),Map.empty)
667681
-- Failure case, either a cradle error or the none cradle
668682
Left err -> do
669683
if (not $ null extraToLoads)
@@ -676,18 +690,19 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
676690
let failedLoadingFiles = (Set.insert cfp extraToLoads) `Set.difference` old_files
677691
atomicModifyIORef' error_loading_files (\xs -> (failedLoadingFiles <> xs,()))
678692
-- retry without other files
679-
atomically $ forM_ pendingFiles (writeTQueue pendingFilesTQueue)
680693
logWith recorder Info $ LogSessionReloadOnError cfp (Set.toList pendingFiles)
681694
consultCradle hieYaml cfp
682695
else do
683-
dep_info <- getDependencyInfo (maybeToList hieYaml)
696+
dep_info <- getDependencyInfo ((maybeToList hieYaml) ++ concatMap cradleErrorDependencies err)
684697
let ncfp = toNormalizedFilePath' cfp
685698
let res = (map (\err' -> renderCradleError err' cradle ncfp) err, Nothing)
686-
void $ modifyVar' fileToFlags $
687-
Map.insertWith HM.union hieYaml (HM.singleton ncfp (res, dep_info))
688-
void $ modifyVar' filesMap $ HM.insert ncfp hieYaml
699+
-- remove cfp from pending files
700+
atomically $ S.delete cfp pendingFileSet
701+
atomically $ do
702+
STM.focus (Focus.insertOrMerge HM.union (HM.singleton ncfp (res, dep_info))) hieYaml fileToFlags
703+
STM.insert hieYaml ncfp filesMap
689704
atomicModifyIORef' error_loading_files (\xs -> (Set.insert cfp xs,()))
690-
return (res, maybe [] pure hieYaml ++ concatMap cradleErrorDependencies err)
705+
return (res, dep_info)
691706

692707
let
693708
-- | We allow users to specify a loading strategy.
@@ -710,21 +725,22 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
710725
-- This caches the mapping from hie.yaml + Mod.hs -> [String]
711726
-- Returns the Ghc session and the cradle dependencies
712727
let sessionOpts :: (Maybe FilePath, FilePath)
713-
-> IO (IdeResult HscEnvEq, [FilePath])
728+
-> IO (IdeResult HscEnvEq, DependencyInfo)
714729
sessionOpts (hieYaml, file) = do
715730
Extra.whenM didSessionLoadingPreferenceConfigChange $ do
716731
logWith recorder Info LogSessionLoadingChanged
717732
-- If the dependencies are out of date then clear both caches and start
718733
-- again.
719-
modifyVar_ fileToFlags (const (return Map.empty))
720-
modifyVar_ filesMap (const (return HM.empty))
734+
atomically $ do
735+
STM.reset filesMap
736+
STM.reset fileToFlags
721737
-- Don't even keep the name cache, we start from scratch here!
722738
modifyVar_ hscEnvs (const (return Map.empty))
723739
-- cleanup error loading files and cradle files
724740
atomicModifyIORef' error_loading_files (\_ -> (Set.empty,()))
725741
atomicModifyIORef' cradle_files (\_ -> (Set.empty,()))
726742

727-
v <- Map.findWithDefault HM.empty hieYaml <$> readVar fileToFlags
743+
v <- atomically $ fromMaybe HM.empty <$> STM.lookup hieYaml fileToFlags
728744
case HM.lookup (toNormalizedFilePath' file) v of
729745
Just (opts, old_di) -> do
730746
deps_ok <- checkDependencyInfo old_di
@@ -735,31 +751,77 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
735751
atomicModifyIORef' cradle_files (\xs -> (Set.delete file xs,()))
736752
-- If the dependencies are out of date then clear both caches and start
737753
-- again.
738-
modifyVar_ fileToFlags (const (return Map.empty))
739-
modifyVar_ filesMap (const (return HM.empty))
754+
atomically $ do
755+
STM.reset filesMap
756+
STM.reset fileToFlags
740757
-- Keep the same name cache
741758
modifyVar_ hscEnvs (return . Map.adjust (const []) hieYaml )
742759
consultCradle hieYaml file
743-
else return (opts, Map.keys old_di)
760+
else return (opts, old_di)
744761
Nothing -> consultCradle hieYaml file
745762

763+
let checkInCache ::NormalizedFilePath -> STM (Maybe (IdeResult HscEnvEq, DependencyInfo))
764+
checkInCache ncfp = runMaybeT $ do
765+
cachedHieYamlLocation <- MaybeT $ STM.lookup ncfp filesMap
766+
m <- MaybeT $ STM.lookup cachedHieYamlLocation fileToFlags
767+
MaybeT $ pure $ HM.lookup ncfp m
768+
746769
-- The main function which gets options for a file. We only want one of these running
747770
-- at a time. Therefore the IORef contains the currently running cradle, if we try
748771
-- to get some more options then we wait for the currently running action to finish
749772
-- before attempting to do so.
750-
let getOptions :: FilePath -> IO (IdeResult HscEnvEq, [FilePath])
773+
let getOptions :: FilePath -> IO (IdeResult HscEnvEq, DependencyInfo)
751774
getOptions file = do
752775
let ncfp = toNormalizedFilePath' file
753-
cachedHieYamlLocation <- HM.lookup ncfp <$> readVar filesMap
776+
cachedHieYamlLocation <- atomically $ STM.lookup ncfp filesMap
754777
hieYaml <- cradleLoc file
755-
sessionOpts (join cachedHieYamlLocation <|> hieYaml, file) `Safe.catch` \e ->
756-
return (([renderPackageSetupException file e], Nothing), maybe [] pure hieYaml)
757-
778+
let hieLoc = join cachedHieYamlLocation <|> hieYaml
779+
result <- sessionOpts (hieLoc, file) `Safe.catch` \e -> do
780+
dep <- getDependencyInfo $ maybe [] pure hieYaml
781+
return (([renderPackageSetupException file e], Nothing), dep)
782+
atomically $ STM.focus (Focus.insertOrMerge HM.union (HM.singleton ncfp result)) hieLoc fileToFlags
783+
return result
784+
785+
let getOptionsLoop :: IO ()
786+
getOptionsLoop = do
787+
-- Get the next file to load
788+
absFile <- atomically $ S.readQueue pendingFileSet
789+
logWith recorder Info (LogGetOptionsLoop absFile)
790+
void $ getOptions absFile
791+
getOptionsLoop
792+
793+
let getSessionRetry :: FilePath -> IO (IdeResult HscEnvEq, DependencyInfo)
794+
getSessionRetry absFile = do
795+
let ncfp = toNormalizedFilePath' absFile
796+
-- check if in the cache
797+
res <- atomically $ checkInCache ncfp
798+
logWith recorder Info $ LogGetSessionRetry absFile
799+
updateDateRes <- case res of
800+
Just r -> do
801+
depOk <- checkDependencyInfo (snd r)
802+
if depOk
803+
then return $ Just r
804+
else return Nothing
805+
_ -> return Nothing
806+
case updateDateRes of
807+
Just r -> return r
808+
Nothing -> do
809+
-- if not ok, we need to reload the session
810+
atomically $ do
811+
S.insert absFile pendingFileSet
812+
atomically $ do
813+
-- wait until pendingFiles is not in pendingFiles
814+
Extra.whenM (S.lookup absFile pendingFileSet) STM.retry
815+
getSessionRetry absFile
816+
817+
-- Start the getOptionsLoop if the queue is empty
818+
liftIO $ atomically $ Extra.whenM (isEmptyTQueue que) $ writeTQueue que getOptionsLoop
758819
returnWithVersion $ \file -> do
759820
let absFile = toAbsolutePath file
760-
atomically $ writeTQueue pendingFilesTQueue absFile
821+
second Map.keys <$> getSessionRetry absFile
822+
-- atomically $ writeTQueue pendingFiles absFile
761823
-- see Note [Serializing runs in separate thread]
762-
awaitRunInThread que $ getOptions absFile
824+
-- awaitRunInThread que $ second Map.keys <$> getOptions absFile
763825

764826
-- | Run the specific cradle on a specific FilePath via hie-bios.
765827
-- This then builds dependencies or whatever based on the cradle, gets the
@@ -1034,10 +1096,11 @@ setCacheDirs recorder CacheDirs{..} dflags = do
10341096
type DependencyInfo = Map.Map FilePath (Maybe UTCTime)
10351097
type HieMap = Map.Map (Maybe FilePath) [RawComponentInfo]
10361098
-- | Maps a "hie.yaml" location to all its Target Filepaths and options.
1037-
type FlagsMap = Map.Map (Maybe FilePath) (HM.HashMap NormalizedFilePath (IdeResult HscEnvEq, DependencyInfo))
1099+
type FlagsMap = STM.Map (Maybe FilePath) (HM.HashMap NormalizedFilePath (IdeResult HscEnvEq, DependencyInfo))
10381100
-- | Maps a Filepath to its respective "hie.yaml" location.
10391101
-- It aims to be the reverse of 'FlagsMap'.
1040-
type FilesMap = HM.HashMap NormalizedFilePath (Maybe FilePath)
1102+
type FilesMap = STM.Map NormalizedFilePath (Maybe FilePath)
1103+
10411104

10421105
-- This is pristine information about a component
10431106
data RawComponentInfo = RawComponentInfo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module Development.IDE.Session.OrderedSet where
2+
3+
import Control.Concurrent.STM (STM, TQueue, newTQueueIO)
4+
import Control.Concurrent.STM.TQueue (readTQueue, writeTQueue)
5+
import Data.Hashable (Hashable)
6+
import qualified ListT as LT
7+
import qualified StmContainers.Set as S
8+
import StmContainers.Set (Set)
9+
10+
11+
type OrderedSet a = (TQueue a, Set a)
12+
13+
insert :: Hashable a => a -> OrderedSet a -> STM ()
14+
insert a (que, s) = do
15+
S.insert a s
16+
writeTQueue que a
17+
return ()
18+
19+
newIO :: Hashable a => IO (OrderedSet a)
20+
newIO = do
21+
que <- newTQueueIO
22+
s <- S.newIO
23+
return (que, s)
24+
25+
readQueue :: Hashable a => OrderedSet a -> STM a
26+
readQueue rs@(que, s) = do
27+
f <- readTQueue que
28+
b <- S.lookup f s
29+
-- retry if the file is already in done
30+
if b then return f else readQueue rs
31+
32+
lookup :: Hashable a => a -> OrderedSet a -> STM Bool
33+
lookup a (_, s) = S.lookup a s
34+
35+
delete :: Hashable a => a -> OrderedSet a -> STM ()
36+
delete a (_, s) = S.delete a s
37+
38+
toUnOrderedList :: Hashable a => OrderedSet a -> STM [a]
39+
toUnOrderedList (_, s) = LT.toList $ S.listT s

0 commit comments

Comments
 (0)