@@ -25,6 +25,7 @@ import Control.Exception.Safe as Safe
25
25
import Control.Monad
26
26
import Control.Monad.Extra as Extra
27
27
import Control.Monad.IO.Class
28
+ import Control.Monad.Trans.Maybe (MaybeT (MaybeT , runMaybeT ))
28
29
import qualified Crypto.Hash.SHA1 as H
29
30
import Data.Aeson hiding (Error )
30
31
import Data.Bifunctor
@@ -103,8 +104,7 @@ import qualified Data.HashSet as Set
103
104
import qualified Data.Set as OS
104
105
import Database.SQLite.Simple
105
106
import Development.IDE.Core.Tracing (withTrace )
106
- import Development.IDE.Core.WorkerThread (awaitRunInThread ,
107
- withWorkerQueue )
107
+ import Development.IDE.Core.WorkerThread (withWorkerQueue )
108
108
import qualified Development.IDE.GHC.Compat.Util as Compat
109
109
import Development.IDE.Session.Diagnostics (renderCradleError )
110
110
import Development.IDE.Types.Shake (WithHieDb ,
@@ -119,12 +119,17 @@ import qualified System.Random as Random
119
119
import System.Random (RandomGen )
120
120
import Text.ParserCombinators.ReadP (readP_to_S )
121
121
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
122
126
import GHC.Data.Bag
123
127
import GHC.Driver.Env (hsc_all_home_unit_ids )
124
128
import GHC.Driver.Errors.Types
125
129
import GHC.Types.Error (errMsgDiagnostic ,
126
130
singleMessage )
127
131
import GHC.Unit.State
132
+ import qualified StmContainers.Map as STM
128
133
129
134
data Log
130
135
= LogSettingInitialDynFlags
@@ -148,10 +153,14 @@ data Log
148
153
| LogSessionLoadingChanged
149
154
| LogSessionNewLoadedFiles ! [FilePath ]
150
155
| LogSessionReloadOnError FilePath ! [FilePath ]
156
+ | LogGetOptionsLoop ! FilePath
157
+ | LogGetSessionRetry ! FilePath
151
158
deriving instance Show Log
152
159
153
160
instance Pretty Log where
154
161
pretty = \ case
162
+ LogGetSessionRetry path -> " Retrying get session for" <+> pretty path
163
+ LogGetOptionsLoop fp -> " Loop: getOptions for" <+> pretty fp
155
164
LogSessionReloadOnError path files ->
156
165
" Reloading file due to error in" <+> pretty path <+> " with files:" <+> pretty files
157
166
LogSessionNewLoadedFiles files ->
@@ -435,14 +444,14 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
435
444
-- Mapping from hie.yaml file to HscEnv, one per hie.yaml file
436
445
hscEnvs <- newVar Map. empty :: IO (Var HieMap )
437
446
-- Mapping from a Filepath to HscEnv
438
- fileToFlags <- newVar Map. empty :: IO ( Var FlagsMap )
447
+ fileToFlags <- STM. newIO :: IO FlagsMap
439
448
-- Mapping from a Filepath to its 'hie.yaml' location.
440
449
-- Should hold the same Filepaths as 'fileToFlags', otherwise
441
450
-- they are inconsistent. So, everywhere you modify 'fileToFlags',
442
451
-- you have to modify 'filesMap' as well.
443
- filesMap <- newVar HM. empty :: IO ( Var FilesMap )
452
+ filesMap <- STM. newIO :: IO FilesMap
444
453
-- Pending files waiting to be loaded
445
- pendingFilesTQueue <- newTQueueIO
454
+ pendingFileSet <- S. newIO :: IO ( S. OrderedSet FilePath )
446
455
-- Version of the mappings above
447
456
version <- newVar 0
448
457
biosSessionLoadingVar <- newVar Nothing :: IO (Var (Maybe SessionLoadingPreferenceConfig ))
@@ -559,7 +568,7 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
559
568
560
569
561
570
let session :: (Maybe FilePath , NormalizedFilePath , ComponentOptions , FilePath )
562
- -> IO ((IdeResult HscEnvEq ,[ FilePath ] ), HashSet FilePath )
571
+ -> IO ((IdeResult HscEnvEq ,DependencyInfo ), HashSet FilePath )
563
572
session args@ (hieYaml, _cfp, _opts, _libDir) = do
564
573
(new_deps, old_deps) <- packageSetup args
565
574
@@ -589,8 +598,11 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
589
598
, " If you are using a .cabal file, please ensure that this module is listed in either the exposed-modules or other-modules section"
590
599
]
591
600
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
+
594
606
-- Typecheck all files in the project on startup
595
607
checkProject <- getCheckProject
596
608
-- The VFS doesn't change on cradle edits, re-use the old one.
@@ -609,9 +621,9 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
609
621
let ! exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces
610
622
liftIO $ atomically $ modifyTVar' (exportsMap shakeExtras) (exportsMap' <> )
611
623
return [keys1, keys2]
612
- return $ (second Map. keys this_options, Set. fromList $ fromNormalizedFilePath <$> newLoaded)
624
+ return $ (this_options, Set. fromList $ fromNormalizedFilePath <$> newLoaded)
613
625
614
- let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq , [ FilePath ] )
626
+ let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq , DependencyInfo )
615
627
consultCradle hieYaml cfp = do
616
628
let lfpLog = makeRelative rootDir cfp
617
629
logWith recorder Info $ LogCradlePath lfpLog
@@ -625,7 +637,7 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
625
637
let progMsg = " Setting up " <> T. pack (takeBaseName (cradleRootDir cradle))
626
638
<> " (for " <> T. pack lfpLog <> " )"
627
639
628
- pendingFiles <- Set. insert cfp . Set. fromList <$> (atomically $ flushTQueue pendingFilesTQueue )
640
+ pendingFiles <- Set. insert cfp . Set. fromList <$> (atomically $ S. toUnOrderedList pendingFileSet )
629
641
errorFiles <- readIORef error_loading_files
630
642
old_files <- readIORef cradle_files
631
643
-- 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
652
664
((runTime, _): _)
653
665
| compileTime == runTime -> do
654
666
(results, allNewLoaded) <- session (hieYaml, toNormalizedFilePath' cfp, opts, libDir)
655
- -- put back to pending que if not listed in the results
656
667
-- delete cfp even if we report No cradle target found for the cfp
657
- let remainPendingFiles = Set. delete cfp $ pendingFiles `Set.difference` allNewLoaded
658
668
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
660
671
-- log new loaded files
661
672
logWith recorder Info $ LogSessionNewLoadedFiles $ Set. toList newLoaded
662
673
-- remove all new loaded file from error loading files
663
674
atomicModifyIORef' error_loading_files (\ old -> (old `Set.difference` allNewLoaded, () ))
664
675
atomicModifyIORef' cradle_files (\ xs -> (newLoaded <> xs,() ))
665
676
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)
667
681
-- Failure case, either a cradle error or the none cradle
668
682
Left err -> do
669
683
if (not $ null extraToLoads)
@@ -676,18 +690,19 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
676
690
let failedLoadingFiles = (Set. insert cfp extraToLoads) `Set.difference` old_files
677
691
atomicModifyIORef' error_loading_files (\ xs -> (failedLoadingFiles <> xs,() ))
678
692
-- retry without other files
679
- atomically $ forM_ pendingFiles (writeTQueue pendingFilesTQueue)
680
693
logWith recorder Info $ LogSessionReloadOnError cfp (Set. toList pendingFiles)
681
694
consultCradle hieYaml cfp
682
695
else do
683
- dep_info <- getDependencyInfo (maybeToList hieYaml)
696
+ dep_info <- getDependencyInfo (( maybeToList hieYaml) ++ concatMap cradleErrorDependencies err )
684
697
let ncfp = toNormalizedFilePath' cfp
685
698
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
689
704
atomicModifyIORef' error_loading_files (\ xs -> (Set. insert cfp xs,() ))
690
- return (res, maybe [] pure hieYaml ++ concatMap cradleErrorDependencies err )
705
+ return (res, dep_info )
691
706
692
707
let
693
708
-- | We allow users to specify a loading strategy.
@@ -710,21 +725,22 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
710
725
-- This caches the mapping from hie.yaml + Mod.hs -> [String]
711
726
-- Returns the Ghc session and the cradle dependencies
712
727
let sessionOpts :: (Maybe FilePath , FilePath )
713
- -> IO (IdeResult HscEnvEq , [ FilePath ] )
728
+ -> IO (IdeResult HscEnvEq , DependencyInfo )
714
729
sessionOpts (hieYaml, file) = do
715
730
Extra. whenM didSessionLoadingPreferenceConfigChange $ do
716
731
logWith recorder Info LogSessionLoadingChanged
717
732
-- If the dependencies are out of date then clear both caches and start
718
733
-- 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
721
737
-- Don't even keep the name cache, we start from scratch here!
722
738
modifyVar_ hscEnvs (const (return Map. empty))
723
739
-- cleanup error loading files and cradle files
724
740
atomicModifyIORef' error_loading_files (\ _ -> (Set. empty,() ))
725
741
atomicModifyIORef' cradle_files (\ _ -> (Set. empty,() ))
726
742
727
- v <- Map. findWithDefault HM. empty hieYaml <$> readVar fileToFlags
743
+ v <- atomically $ fromMaybe HM. empty <$> STM. lookup hieYaml fileToFlags
728
744
case HM. lookup (toNormalizedFilePath' file) v of
729
745
Just (opts, old_di) -> do
730
746
deps_ok <- checkDependencyInfo old_di
@@ -735,31 +751,77 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
735
751
atomicModifyIORef' cradle_files (\ xs -> (Set. delete file xs,() ))
736
752
-- If the dependencies are out of date then clear both caches and start
737
753
-- 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
740
757
-- Keep the same name cache
741
758
modifyVar_ hscEnvs (return . Map. adjust (const [] ) hieYaml )
742
759
consultCradle hieYaml file
743
- else return (opts, Map. keys old_di)
760
+ else return (opts, old_di)
744
761
Nothing -> consultCradle hieYaml file
745
762
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
+
746
769
-- The main function which gets options for a file. We only want one of these running
747
770
-- at a time. Therefore the IORef contains the currently running cradle, if we try
748
771
-- to get some more options then we wait for the currently running action to finish
749
772
-- before attempting to do so.
750
- let getOptions :: FilePath -> IO (IdeResult HscEnvEq , [ FilePath ] )
773
+ let getOptions :: FilePath -> IO (IdeResult HscEnvEq , DependencyInfo )
751
774
getOptions file = do
752
775
let ncfp = toNormalizedFilePath' file
753
- cachedHieYamlLocation <- HM .lookup ncfp <$> readVar filesMap
776
+ cachedHieYamlLocation <- atomically $ STM .lookup ncfp filesMap
754
777
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
758
819
returnWithVersion $ \ file -> do
759
820
let absFile = toAbsolutePath file
760
- atomically $ writeTQueue pendingFilesTQueue absFile
821
+ second Map. keys <$> getSessionRetry absFile
822
+ -- atomically $ writeTQueue pendingFiles absFile
761
823
-- see Note [Serializing runs in separate thread]
762
- awaitRunInThread que $ getOptions absFile
824
+ -- awaitRunInThread que $ second Map.keys <$> getOptions absFile
763
825
764
826
-- | Run the specific cradle on a specific FilePath via hie-bios.
765
827
-- This then builds dependencies or whatever based on the cradle, gets the
@@ -1034,10 +1096,11 @@ setCacheDirs recorder CacheDirs{..} dflags = do
1034
1096
type DependencyInfo = Map. Map FilePath (Maybe UTCTime )
1035
1097
type HieMap = Map. Map (Maybe FilePath ) [RawComponentInfo ]
1036
1098
-- | 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 ))
1038
1100
-- | Maps a Filepath to its respective "hie.yaml" location.
1039
1101
-- 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
+
1041
1104
1042
1105
-- This is pristine information about a component
1043
1106
data RawComponentInfo = RawComponentInfo
0 commit comments