@@ -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
@@ -277,16 +273,15 @@ data HieDbWriter
277273-- | Actions to queue up on the index worker thread
278274-- The inner `(HieDb -> IO ()) -> IO ()` wraps `HieDb -> IO ()`
279275-- with (currently) retry functionality
280- type IndexQueue = TaskQueue (((HieDb -> IO () ) -> IO () ) -> IO () )
281- type RestartQueue = TaskQueue (IO () )
282- type LoaderQueue = TaskQueue (IO () )
283-
284-
285- data ThreadQueue = ThreadQueue {
286- tIndexQueue :: IndexQueue
287- , tRestartSlot :: RestartSlot
288- , tLoaderQueue :: LoaderQueue
289- }
276+ type IndexQueue = WorkerTasks STM (((HieDb -> IO () ) -> IO () ) -> IO () )
277+ type RestartRef = WorkerTasks STM PendingRestart
278+ type LoaderQueue = WorkerTasks STM (IO () )
279+
280+ data ThreadQueue = ThreadQueue
281+ { tIndexQueue :: IndexQueue
282+ , tRestartSlot :: RestartSlot
283+ , tLoaderQueue :: LoaderQueue
284+ }
290285
291286-- Note [Semantic Tokens Cache Location]
292287-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -826,23 +821,21 @@ data PendingRestart = PendingRestart
826821 , pendingRestartDoneSignals :: ! [TMVar () ]
827822 }
828823
829- newestVFSModified :: VFSModified -> VFSModified -> VFSModified
830- newestVFSModified VFSUnmodified old = old
831- newestVFSModified new@ (VFSModified _) _ = new
832-
833- mergePendingRestart :: PendingRestart -> Maybe PendingRestart -> PendingRestart
834- mergePendingRestart new Nothing = new
835- mergePendingRestart new (Just old) = PendingRestart
824+ instance Semigroup PendingRestart where
825+ new <> old = PendingRestart
836826 { pendingRestartVFS = newestVFSModified (pendingRestartVFS new) (pendingRestartVFS old)
837827 , pendingRestartReasons = pendingRestartReasons new ++ pendingRestartReasons old
838828 , pendingRestartActions = pendingRestartActions new ++ pendingRestartActions old
839829 , pendingRestartActionBetweenSessions = pendingRestartActionBetweenSessions new ++ pendingRestartActionBetweenSessions old
840830 , pendingRestartDoneSignals = pendingRestartDoneSignals new ++ pendingRestartDoneSignals old
841831 }
842832
833+ newestVFSModified :: VFSModified -> VFSModified -> VFSModified
834+ newestVFSModified VFSUnmodified old = old
835+ newestVFSModified new@ (VFSModified _) _ = new
836+
843837data RestartSlot = RestartSlot
844- { queuedRestart :: IORef (Maybe PendingRestart )
845- , restartSignal :: MVar ()
838+ { restartRef :: WorkerTasks STM PendingRestart
846839 , lastRestartBarrier :: TVar (TMVar () )
847840 -- ^ A barrier that is filled when the most recent shake restart completes.
848841 --
@@ -851,77 +844,62 @@ data RestartSlot = RestartSlot
851844 -- restart can then wait on this.
852845 }
853846
854- newRestartSlot :: IO RestartSlot
855- newRestartSlot = do
847+ newRestartSlot :: RestartRef -> IO RestartSlot
848+ newRestartSlot queuedRestart = do
856849 initialBarrier <- newTMVarIO () -- starts filled (no pending restart)
857- RestartSlot <$> newIORef Nothing <*> newEmptyMVar <*> newTVarIO initialBarrier
850+ RestartSlot <$> pure queuedRestart <*> newTVarIO initialBarrier
858851
859852-- | Restart the current 'ShakeSession' with the given system actions.
860853--
861854-- Any actions running in the current session will be aborted, but actions added
862855-- via 'shakeEnqueue' will be requeued.
863856shakeRestart :: IdeState -> VFSModified -> T. Text -> [DelayedAction () ] -> IO [Key ] -> IO ()
864857shakeRestart IdeState {.. } vfs reason acts ioActionBetweenShakeSession = do
865- restartDone <- newEmptyTMVarIO
866- let slot = restartSlot shakeExtras
867- -- Publish this restart's barrier, that dependents LSP requests can wait on.
868- atomically $ writeTVar (lastRestartBarrier slot) restartDone
869- atomicModifyIORef'_ (queuedRestart slot) $ Just . mergePendingRestart PendingRestart
870- { pendingRestartVFS = vfs
871- , pendingRestartActionBetweenSessions = [ioActionBetweenShakeSession]
872- , pendingRestartReasons = [reason]
873- , pendingRestartActions = acts
874- , pendingRestartDoneSignals = [restartDone]
875- }
876- void $ tryPutMVar (restartSignal slot) ()
877- -- Block until the restart (including ioActionBetweenShakeSession) completes.
878- -- This preserves the invariant from the original synchronous shakeRestart:
879- -- callers (e.g. the session loader) must not proceed until their
880- -- between-session actions have run, otherwise downstream rules can observe
881- -- stale results (see Note at Session.hs restartSession call site).
882- atomically $ readTMVar restartDone
883-
884- -- | Run a worker that asynchronously processes shake restart requests. Will
885- -- only ever queue upto 1 additional restart, accumulating data while processing
886- -- any restart.
887- withRestartWorker :: MVar IdeState -> IO r -> IO r
888- withRestartWorker ideMVar action = do
889- let restartWorkerAction = do
890- ide@ IdeState {.. } <- readMVar ideMVar
891- forever (processPendingRestart (shakeRecorder shakeExtras) ide)
892- `catch` \ (e :: SomeException ) ->
893- case fromException e of
894- Just AsyncCancelled -> throwIO e
895- _ -> logWith (shakeRecorder shakeExtras) Error (LogRestartWorkerException e)
896-
897- withAsync restartWorkerAction $ \ _ -> action
898-
899- processPendingRestart :: Recorder (WithPriority Log ) -> IdeState -> IO ()
900- processPendingRestart recorder IdeState {.. } = do
901- takeMVar (restartSignal (restartSlot shakeExtras))
902- pendingRestart <- atomicModifyIORef' (queuedRestart (restartSlot shakeExtras)) (Nothing ,)
903- void $ forM pendingRestart $ \ PendingRestart {.. } -> do
904- flip finally (atomically $ traverse (flip tryPutTMVar () ) (reverse pendingRestartDoneSignals)) $ do
905- let sessionAction runner = do
906- (stopTime,() ) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
907- keys <- fmap concat (sequence (reverse pendingRestartActionBetweenSessions))
908- -- it is every important to update the dirty keys after we enter the critical section
909- -- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
910- atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \ x -> foldl' (flip insertKeySet) x keys
911- res <- shakeDatabaseProfile shakeDb
912- backlog <- readTVarIO $ dirtyKeys shakeExtras
913- queue <- atomicallyNamed " actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
914-
915- -- this log is required by tests
916- logWith recorder Debug $ LogBuildSessionRestart (reverse pendingRestartReasons) queue backlog stopTime res
917-
918- withMVar' shakeSession sessionAction $ \ () ->
919- -- It is crucial to be masked here, otherwise we can get killed
920- -- between spawning the new thread and updating shakeSession.
921- -- See https://github.com/haskell/ghcide/issues/79
922- (,() ) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb
923- (reverse pendingRestartActions)
924- (reverse pendingRestartReasons)
858+ restartDone <- newEmptyTMVarIO
859+ let RestartSlot {.. } = restartSlot shakeExtras
860+ -- Publish this restart's barrier, that dependents LSP requests can wait on.
861+ atomically $ do
862+ writeTVar lastRestartBarrier restartDone
863+ addWorkerTask restartRef $ PendingRestart
864+ { pendingRestartVFS = vfs
865+ , pendingRestartActionBetweenSessions = [ioActionBetweenShakeSession]
866+ , pendingRestartReasons = [reason]
867+ , pendingRestartActions = acts
868+ , pendingRestartDoneSignals = [restartDone]
869+ }
870+
871+ processPendingRestart :: Recorder (WithPriority Log ) -> MVar IdeState -> PendingRestart -> IO ()
872+ processPendingRestart recorder ideMVar pendingRestart = do
873+ processPendingRestart' recorder ideMVar pendingRestart
874+ `catch` \ (e :: SomeException ) ->
875+ case fromException e of
876+ Just AsyncCancelled -> throwIO e
877+ _ -> logWith recorder Error (LogRestartWorkerException e)
878+
879+ processPendingRestart' :: Recorder (WithPriority Log ) -> MVar IdeState -> PendingRestart -> IO ()
880+ processPendingRestart' recorder ideMVar PendingRestart {.. } = do
881+ IdeState {.. } <- readMVar ideMVar
882+ flip finally (atomically $ traverse (flip tryPutTMVar () ) (reverse pendingRestartDoneSignals)) $ do
883+ let sessionAction runner = do
884+ (stopTime,() ) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
885+ keys <- fmap concat (sequence (reverse pendingRestartActionBetweenSessions))
886+ -- it is every important to update the dirty keys after we enter the critical section
887+ -- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
888+ atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \ x -> foldl' (flip insertKeySet) x keys
889+ res <- shakeDatabaseProfile shakeDb
890+ backlog <- readTVarIO $ dirtyKeys shakeExtras
891+ queue <- atomicallyNamed " actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
892+
893+ -- this log is required by tests
894+ logWith recorder Debug $ LogBuildSessionRestart (reverse pendingRestartReasons) queue backlog stopTime res
895+
896+ withMVar' shakeSession sessionAction $ \ () ->
897+ -- It is crucial to be masked here, otherwise we can get killed
898+ -- between spawning the new thread and updating shakeSession.
899+ -- See https://github.com/haskell/ghcide/issues/79
900+ (,() ) <$> newSession recorder shakeExtras pendingRestartVFS shakeDb
901+ (reverse pendingRestartActions)
902+ (reverse pendingRestartReasons)
925903 where
926904 logErrorAfter :: Seconds -> IO () -> IO ()
927905 logErrorAfter seconds action = flip withAsync (const action) $ do
0 commit comments