@@ -25,6 +25,7 @@ import Control.Exception.Safe as Safe
2525import Control.Monad
2626import Control.Monad.Extra as Extra
2727import Control.Monad.IO.Class
28+ import Control.Monad.Trans.Maybe (MaybeT (MaybeT , runMaybeT ))
2829import qualified Crypto.Hash.SHA1 as H
2930import Data.Aeson hiding (Error )
3031import Data.Bifunctor
@@ -103,8 +104,7 @@ import qualified Data.HashSet as Set
103104import qualified Data.Set as OS
104105import Database.SQLite.Simple
105106import Development.IDE.Core.Tracing (withTrace )
106- import Development.IDE.Core.WorkerThread (awaitRunInThread ,
107- withWorkerQueue )
107+ import Development.IDE.Core.WorkerThread (withWorkerQueue )
108108import qualified Development.IDE.GHC.Compat.Util as Compat
109109import Development.IDE.Session.Diagnostics (renderCradleError )
110110import Development.IDE.Types.Shake (WithHieDb ,
@@ -119,12 +119,17 @@ import qualified System.Random as Random
119119import System.Random (RandomGen )
120120import 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
122126import GHC.Data.Bag
123127import GHC.Driver.Env (hsc_all_home_unit_ids )
124128import GHC.Driver.Errors.Types
125129import GHC.Types.Error (errMsgDiagnostic ,
126130 singleMessage )
127131import GHC.Unit.State
132+ import qualified StmContainers.Map as STM
128133
129134data Log
130135 = LogSettingInitialDynFlags
@@ -148,10 +153,14 @@ data Log
148153 | LogSessionLoadingChanged
149154 | LogSessionNewLoadedFiles ! [FilePath ]
150155 | LogSessionReloadOnError FilePath ! [FilePath ]
156+ | LogGetOptionsLoop ! FilePath
157+ | LogGetSessionRetry ! FilePath
151158deriving instance Show Log
152159
153160instance 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
10341096type DependencyInfo = Map. Map FilePath (Maybe UTCTime )
10351097type 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
10431106data RawComponentInfo = RawComponentInfo
0 commit comments