@@ -26,10 +26,10 @@ module Development.IDE.Core.Shake(
2626 IdeState , shakeSessionInit , shakeExtras , shakeDb , rootDir ,
2727 ShakeExtras (.. ), getShakeExtras , getShakeExtrasRules ,
2828 KnownTargets (.. ), Target (.. ), toKnownFiles , unionKnownTargets , mkKnownTargets ,
29- IdeRule , IdeResult , RestartQueue ,
29+ IdeRule , IdeResult ,
3030 GetModificationTime (GetModificationTime , GetModificationTime_ , missingFileDiagnostics ),
3131 shakeOpen , shakeShut ,
32- withRestartWorker , newRestartSlot ,
32+ newRestartSlot ,
3333 shakeEnqueue ,
3434 newSession ,
3535 use , useNoFile , uses , useWithStaleFast , useWithStaleFast' , delayedAction ,
@@ -76,14 +76,14 @@ module Development.IDE.Core.Shake(
7676 RestartSlot (.. ),
7777 addPersistentRule ,
7878 newestVFSModified ,
79- mergePendingRestart ,
8079 garbageCollectDirtyKeys ,
8180 garbageCollectDirtyKeysOlderThan ,
8281 Log (.. ),
8382 VFSModified (.. ), getClientConfigAction ,
8483 ThreadQueue (.. ),
8584 runWithSignal ,
86- askShake
85+ askShake ,
86+ processPendingRestart
8787 ) where
8888
8989import Control.Concurrent.Async
@@ -149,7 +149,6 @@ import Development.IDE.GHC.Compat (NameCache,
149149 initNameCache ,
150150 knownKeyNames )
151151#endif
152- import Data.IORef.Extra (atomicModifyIORef'_ )
153152import qualified Data.Text.Encoding as T
154153import Development.IDE.GHC.Orphans ()
155154import Development.IDE.Graph hiding (ShakeValue ,
@@ -192,10 +191,7 @@ import qualified StmContainers.Map as STM
192191import System.FilePath hiding (makeRelative )
193192import System.IO.Unsafe (unsafePerformIO )
194193import System.Time.Extra
195- import UnliftIO (IORef ,
196- MonadUnliftIO (withRunInIO ),
197- atomicModifyIORef' ,
198- newIORef )
194+ import UnliftIO (MonadUnliftIO (.. ))
199195
200196
201197data Log
@@ -275,16 +271,15 @@ data HieDbWriter
275271-- | Actions to queue up on the index worker thread
276272-- The inner `(HieDb -> IO ()) -> IO ()` wraps `HieDb -> IO ()`
277273-- with (currently) retry functionality
278- type IndexQueue = TaskQueue (((HieDb -> IO () ) -> IO () ) -> IO () )
279- type RestartQueue = TaskQueue (IO () )
280- type LoaderQueue = TaskQueue (IO () )
281-
282-
283- data ThreadQueue = ThreadQueue {
284- tIndexQueue :: IndexQueue
285- , tRestartSlot :: RestartSlot
286- , tLoaderQueue :: LoaderQueue
287- }
274+ type IndexQueue = WorkerTasks STM (((HieDb -> IO () ) -> IO () ) -> IO () )
275+ type RestartRef = WorkerTasks STM PendingRestart
276+ type LoaderQueue = WorkerTasks STM (IO () )
277+
278+ data ThreadQueue = ThreadQueue
279+ { tIndexQueue :: IndexQueue
280+ , tRestartSlot :: RestartSlot
281+ , tLoaderQueue :: LoaderQueue
282+ }
288283
289284-- Note [Semantic Tokens Cache Location]
290285-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -824,23 +819,21 @@ data PendingRestart = PendingRestart
824819 , pendingRestartDoneSignals :: ! [TMVar () ]
825820 }
826821
827- newestVFSModified :: VFSModified -> VFSModified -> VFSModified
828- newestVFSModified VFSUnmodified old = old
829- newestVFSModified new@ (VFSModified _) _ = new
830-
831- mergePendingRestart :: PendingRestart -> Maybe PendingRestart -> PendingRestart
832- mergePendingRestart new Nothing = new
833- mergePendingRestart new (Just old) = PendingRestart
822+ instance Semigroup PendingRestart where
823+ new <> old = PendingRestart
834824 { pendingRestartVFS = newestVFSModified (pendingRestartVFS new) (pendingRestartVFS old)
835825 , pendingRestartReasons = pendingRestartReasons new ++ pendingRestartReasons old
836826 , pendingRestartActions = pendingRestartActions new ++ pendingRestartActions old
837827 , pendingRestartActionBetweenSessions = pendingRestartActionBetweenSessions new ++ pendingRestartActionBetweenSessions old
838828 , pendingRestartDoneSignals = pendingRestartDoneSignals new ++ pendingRestartDoneSignals old
839829 }
840830
831+ newestVFSModified :: VFSModified -> VFSModified -> VFSModified
832+ newestVFSModified VFSUnmodified old = old
833+ newestVFSModified new@ (VFSModified _) _ = new
834+
841835data RestartSlot = RestartSlot
842- { queuedRestart :: IORef (Maybe PendingRestart )
843- , restartSignal :: MVar ()
836+ { restartRef :: WorkerTasks STM PendingRestart
844837 , lastRestartBarrier :: TVar (TMVar () )
845838 -- ^ A barrier that is filled when the most recent shake restart completes.
846839 --
@@ -849,77 +842,62 @@ data RestartSlot = RestartSlot
849842 -- restart can then wait on this.
850843 }
851844
852- newRestartSlot :: IO RestartSlot
853- newRestartSlot = do
845+ newRestartSlot :: RestartRef -> IO RestartSlot
846+ newRestartSlot queuedRestart = do
854847 initialBarrier <- newTMVarIO () -- starts filled (no pending restart)
855- RestartSlot <$> newIORef Nothing <*> newEmptyMVar <*> newTVarIO initialBarrier
848+ RestartSlot <$> pure queuedRestart <*> newTVarIO initialBarrier
856849
857850-- | Restart the current 'ShakeSession' with the given system actions.
858851--
859852-- Any actions running in the current session will be aborted, but actions added
860853-- via 'shakeEnqueue' will be requeued.
861854shakeRestart :: IdeState -> VFSModified -> T. Text -> [DelayedAction () ] -> IO [Key ] -> IO ()
862855shakeRestart IdeState {.. } vfs reason acts ioActionBetweenShakeSession = do
863- restartDone <- newEmptyTMVarIO
864- let slot = restartSlot shakeExtras
865- -- Publish this restart's barrier, that dependents LSP requests can wait on.
866- atomically $ writeTVar (lastRestartBarrier slot) restartDone
867- atomicModifyIORef'_ (queuedRestart slot) $ Just . mergePendingRestart PendingRestart
868- { pendingRestartVFS = vfs
869- , pendingRestartActionBetweenSessions = [ioActionBetweenShakeSession]
870- , pendingRestartReasons = [reason]
871- , pendingRestartActions = acts
872- , pendingRestartDoneSignals = [restartDone]
873- }
874- void $ tryPutMVar (restartSignal slot) ()
875- -- Block until the restart (including ioActionBetweenShakeSession) completes.
876- -- This preserves the invariant from the original synchronous shakeRestart:
877- -- callers (e.g. the session loader) must not proceed until their
878- -- between-session actions have run, otherwise downstream rules can observe
879- -- stale results (see Note at Session.hs restartSession call site).
880- atomically $ readTMVar restartDone
881-
882- -- | Run a worker that asynchronously processes shake restart requests. Will
883- -- only ever queue upto 1 additional restart, accumulating data while processing
884- -- any restart.
885- withRestartWorker :: MVar IdeState -> IO r -> IO r
886- withRestartWorker ideMVar action = do
887- let restartWorkerAction = do
888- ide@ IdeState {.. } <- readMVar ideMVar
889- forever (processPendingRestart (shakeRecorder shakeExtras) ide)
890- `catch` \ (e :: SomeException ) ->
891- case fromException e of
892- Just AsyncCancelled -> throwIO e
893- _ -> logWith (shakeRecorder shakeExtras) Error (LogRestartWorkerException e)
894-
895- withAsync restartWorkerAction $ \ _ -> action
896-
897- processPendingRestart :: Recorder (WithPriority Log ) -> IdeState -> IO ()
898- processPendingRestart recorder IdeState {.. } = do
899- takeMVar (restartSignal (restartSlot shakeExtras))
900- pendingRestart <- atomicModifyIORef' (queuedRestart (restartSlot shakeExtras)) (Nothing ,)
901- void $ forM pendingRestart $ \ PendingRestart {.. } -> do
902- flip finally (atomically $ traverse (flip tryPutTMVar () ) (reverse pendingRestartDoneSignals)) $ do
903- let sessionAction runner = do
904- (stopTime,() ) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
905- keys <- fmap concat (sequence (reverse pendingRestartActionBetweenSessions))
906- -- it is every important to update the dirty keys after we enter the critical section
907- -- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
908- atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \ x -> foldl' (flip insertKeySet) x keys
909- res <- shakeDatabaseProfile shakeDb
910- backlog <- readTVarIO $ dirtyKeys shakeExtras
911- queue <- atomicallyNamed " actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
912-
913- -- this log is required by tests
914- logWith recorder Debug $ LogBuildSessionRestart (reverse pendingRestartReasons) queue backlog stopTime res
915-
916- withMVar' shakeSession sessionAction $ \ () ->
917- -- It is crucial to be masked here, otherwise we can get killed
918- -- between spawning the new thread and updating shakeSession.
919- -- See https://github.com/haskell/ghcide/issues/79
920- (,() ) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb
921- (reverse pendingRestartActions)
922- (reverse pendingRestartReasons)
856+ restartDone <- newEmptyTMVarIO
857+ let RestartSlot {.. } = restartSlot shakeExtras
858+ -- Publish this restart's barrier, that dependents LSP requests can wait on.
859+ atomically $ do
860+ writeTVar lastRestartBarrier restartDone
861+ addWorkerTask restartRef $ PendingRestart
862+ { pendingRestartVFS = vfs
863+ , pendingRestartActionBetweenSessions = [ioActionBetweenShakeSession]
864+ , pendingRestartReasons = [reason]
865+ , pendingRestartActions = acts
866+ , pendingRestartDoneSignals = [restartDone]
867+ }
868+
869+ processPendingRestart :: Recorder (WithPriority Log ) -> MVar IdeState -> PendingRestart -> IO ()
870+ processPendingRestart recorder ideMVar pendingRestart = do
871+ processPendingRestart' recorder ideMVar pendingRestart
872+ `catch` \ (e :: SomeException ) ->
873+ case fromException e of
874+ Just AsyncCancelled -> throwIO e
875+ _ -> logWith recorder Error (LogRestartWorkerException e)
876+
877+ processPendingRestart' :: Recorder (WithPriority Log ) -> MVar IdeState -> PendingRestart -> IO ()
878+ processPendingRestart' recorder ideMVar PendingRestart {.. } = do
879+ IdeState {.. } <- readMVar ideMVar
880+ flip finally (atomically $ traverse (flip tryPutTMVar () ) (reverse pendingRestartDoneSignals)) $ do
881+ let sessionAction runner = do
882+ (stopTime,() ) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
883+ keys <- fmap concat (sequence (reverse pendingRestartActionBetweenSessions))
884+ -- it is every important to update the dirty keys after we enter the critical section
885+ -- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
886+ atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \ x -> foldl' (flip insertKeySet) x keys
887+ res <- shakeDatabaseProfile shakeDb
888+ backlog <- readTVarIO $ dirtyKeys shakeExtras
889+ queue <- atomicallyNamed " actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
890+
891+ -- this log is required by tests
892+ logWith recorder Debug $ LogBuildSessionRestart (reverse pendingRestartReasons) queue backlog stopTime res
893+
894+ withMVar' shakeSession sessionAction $ \ () ->
895+ -- It is crucial to be masked here, otherwise we can get killed
896+ -- between spawning the new thread and updating shakeSession.
897+ -- See https://github.com/haskell/ghcide/issues/79
898+ (,() ) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb
899+ (reverse pendingRestartActions)
900+ (reverse pendingRestartReasons)
923901 where
924902 logErrorAfter :: Seconds -> IO () -> IO ()
925903 logErrorAfter seconds action = flip withAsync (const action) $ do
0 commit comments