Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Soulomoon/shake rebouncer #11

Open
wants to merge 55 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
684a850
passing keys need to be update directly to restartShakeSession
soulomoon Apr 22, 2024
5d09837
send actions to run between restart
soulomoon Apr 24, 2024
13528d7
fix
soulomoon Apr 24, 2024
fdbb7aa
fix
soulomoon Apr 24, 2024
6fc3646
some more fix up
soulomoon Apr 24, 2024
e247ae1
use IO [Key]
soulomoon Apr 24, 2024
7b7ea4d
remove double return
soulomoon Apr 24, 2024
c31a375
Update ghcide/src/Development/IDE/Core/FileExists.hs
soulomoon Apr 26, 2024
bfb06a3
minor fix
soulomoon Apr 26, 2024
8adf5a4
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon Apr 26, 2024
bbc5c95
capture more dirty keys to between sessions
soulomoon Apr 27, 2024
48d5644
cleanup
soulomoon Apr 27, 2024
e967dde
fix the race between cache value updated but not updated hls-database
soulomoon Apr 28, 2024
69c9396
fix build
soulomoon Apr 28, 2024
02f0d41
fix hls-graph
soulomoon Apr 28, 2024
554102d
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon Apr 28, 2024
c983727
fix 9.2.8
soulomoon Apr 28, 2024
3748fc2
format
soulomoon Apr 29, 2024
3107879
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon Apr 30, 2024
a65ac5c
run refreshDeps in a single asyncWithCleanUp
soulomoon May 1, 2024
f7a15cb
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon May 1, 2024
f4690c5
shut the session before shut the reactor
soulomoon May 1, 2024
c6a33cb
Merge remote-tracking branch 'upstream/soulomoon/mark-dirty-keys-sync…
soulomoon May 1, 2024
e6105ff
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon May 2, 2024
610355c
Revert "shut the session before shut the reactor"
soulomoon May 2, 2024
63b1956
remove record dirty key recordDirtyKeys
soulomoon May 2, 2024
4d28344
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon May 4, 2024
2eb29b4
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon May 5, 2024
2c61a63
Merge branch 'master' into soulomoon/mark-dirty-keys-sync-to-hls-graph
soulomoon May 5, 2024
7423695
Update ghcide/src/Development/IDE/Core/Shake.hs
soulomoon May 6, 2024
0c4a2f5
Update ghcide/src/Development/IDE/Core/FileExists.hs
soulomoon May 6, 2024
bea88b5
Update ghcide/session-loader/Development/IDE/Session.hs
soulomoon May 6, 2024
c9219f0
Update ghcide/session-loader/Development/IDE/Session.hs
soulomoon May 6, 2024
7a08b03
cleanup
soulomoon May 6, 2024
dc18b74
fix
soulomoon May 6, 2024
dc71a40
cleanup
soulomoon May 6, 2024
342f52f
fix ghc 9.2
soulomoon May 6, 2024
240254e
stylish
soulomoon May 6, 2024
3bb1e1b
thread shake restart to a worker thread
soulomoon May 6, 2024
f8220ba
wait for cancel
soulomoon May 6, 2024
013650b
wait for cancel
soulomoon May 6, 2024
e10d135
push known targets back to session restart
soulomoon May 6, 2024
39a45a2
wait for the session to be restarted
soulomoon May 6, 2024
6d34bac
split to restarting to stoping thread and starting thread
soulomoon May 7, 2024
f76bff4
handle error during stopShakeSession
soulomoon May 7, 2024
ebfb375
more sync
soulomoon May 7, 2024
5d77f61
no debounce
soulomoon May 7, 2024
89ae61a
Merge branch 'master' into soulomoon/shake-rebouncer
soulomoon May 10, 2024
36651b4
revert
soulomoon May 10, 2024
1d73ef5
revert
soulomoon May 10, 2024
bf8bb34
revert
soulomoon May 10, 2024
06b975a
revert
soulomoon May 10, 2024
79a63e4
revert
soulomoon May 10, 2024
1817b52
Merge branch 'master' into soulomoon/shake-rebouncer
soulomoon May 10, 2024
f387ccd
rename
soulomoon May 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions ghcide/src/Development/IDE/Core/Service.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
module Development.IDE.Core.Service(
getIdeOptions, getIdeOptionsIO,
IdeState, initialise, shutdown,
runWithShake,
ShakeOpQueue,
runAction,
getDiagnostics,
ideLogger,
Expand All @@ -31,13 +33,24 @@ import Ide.Plugin.Config
import qualified Language.LSP.Protocol.Types as LSP
import qualified Language.LSP.Server as LSP

import Control.Concurrent.Async (async, withAsync)
import Control.Concurrent.STM (TQueue, atomically,
flushTQueue, newTQueueIO,
readTQueue, writeTBQueue,
writeTQueue)
import Control.Monad
import qualified Data.List.NonEmpty as NE
import Data.Semigroup (Semigroup (sconcat))
import qualified Data.Text as T
import Debug.Trace (traceM)
import qualified Development.IDE.Core.FileExists as FileExists
import qualified Development.IDE.Core.OfInterest as OfInterest
import Development.IDE.Core.Shake hiding (Log)
import qualified Development.IDE.Core.Shake as Shake
import Development.IDE.Types.Monitoring (Monitoring)
import Development.IDE.Types.Shake (WithHieDb)
import Extra (sleep)
import Ide.Logger (Priority (Info), logWith)
import Ide.Types (IdePlugins)
import System.Environment (lookupEnv)

Expand Down Expand Up @@ -66,9 +79,10 @@ initialise :: Recorder (WithPriority Log)
-> IdeOptions
-> WithHieDb
-> IndexQueue
-> ShakeOpQueue
-> Monitoring
-> IO IdeState
initialise recorder defaultConfig plugins mainRule lspEnv debouncer options withHieDb hiedbChan metrics = do
initialise recorder defaultConfig plugins mainRule lspEnv debouncer options withHieDb hiedbChan sq metrics = do
shakeProfiling <- do
let fromConf = optShakeProfiling options
fromEnv <- lookupEnv "GHCIDE_BUILD_PROFILING"
Expand All @@ -84,6 +98,7 @@ initialise recorder defaultConfig plugins mainRule lspEnv debouncer options with
(optTesting options)
withHieDb
hiedbChan
sq
(optShakeOptions options)
metrics
$ do
Expand All @@ -94,11 +109,13 @@ initialise recorder defaultConfig plugins mainRule lspEnv debouncer options with

-- | Shutdown the Compiler Service.
shutdown :: IdeState -> IO ()
shutdown = shakeShut
shutdown st = shakeShut st

-- This will return as soon as the result of the action is
-- available. There might still be other rules running at this point,
-- e.g., the ofInterestRule.
runAction :: String -> IdeState -> Action a -> IO a
runAction herald ide act =
join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Debug act)


158 changes: 127 additions & 31 deletions ghcide/src/Development/IDE/Core/Shake.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
IdeState, shakeSessionInit, shakeExtras, shakeDb,
ShakeExtras(..), getShakeExtras, getShakeExtrasRules,
KnownTargets, Target(..), toKnownFiles,
IdeRule, IdeResult,
IdeRule, IdeResult, restartRecorder,
GetModificationTime(GetModificationTime, GetModificationTime_, missingFileDiagnostics),
shakeOpen, shakeShut,
shakeOpen, shakeShut, runWithShake,
doShakeRestart,
shakeEnqueue,
ShakeOpQueue,
newSession,
use, useNoFile, uses, useWithStaleFast, useWithStaleFast', delayedAction,
FastResult(..),
Expand Down Expand Up @@ -76,7 +78,11 @@
) where

import Control.Concurrent.Async
import Control.Concurrent.Extra (signalBarrier,
waitBarrier)
import Control.Concurrent.STM

Check warning on line 83 in ghcide/src/Development/IDE/Core/Shake.hs

View workflow job for this annotation

GitHub Actions / Hlint check run

Warning in module Development.IDE.Core.Shake: Use fewer imports ▫︎ Found: "import Control.Concurrent.STM\nimport Control.Concurrent.STM ( readTQueue, writeTQueue )\n" ▫︎ Perhaps: "import Control.Concurrent.STM\n"
import Control.Concurrent.STM (readTQueue,
writeTQueue)
import Control.Concurrent.STM.Stats (atomicallyNamed)
import Control.Concurrent.Strict
import Control.DeepSeq
Expand All @@ -103,10 +109,13 @@
import qualified Data.HashMap.Strict as HMap
import Data.HashSet (HashSet)
import qualified Data.HashSet as HSet
import Data.List.Extra (foldl', partition,
takeEnd)
import Data.List (concat)
import Data.List.Extra (foldl', intercalate,
partition, takeEnd)
import qualified Data.List.NonEmpty as NE
import qualified Data.Map.Strict as Map
import Data.Maybe
import Data.Semigroup (Semigroup (sconcat))
import qualified Data.SortedList as SL
import Data.String (fromString)
import qualified Data.Text as T
Expand All @@ -117,13 +126,14 @@
import Data.Unique
import Data.Vector (Vector)
import qualified Data.Vector as Vector
import Debug.Trace (traceM)
import Development.IDE.Core.Debouncer
import Development.IDE.Core.FileUtils (getModTime)
import Development.IDE.Core.PositionMapping
import Development.IDE.Core.ProgressReporting
import Development.IDE.Core.RuleTypes
import Development.IDE.Core.Tracing
import Development.IDE.GHC.Compat (NameCache,

Check warning on line 136 in ghcide/src/Development/IDE/Core/Shake.hs

View workflow job for this annotation

GitHub Actions / Hlint check run

Warning in module Development.IDE.Core.Shake: Use fewer imports ▫︎ Found: "import Development.IDE.GHC.Compat\n ( NameCache, NameCacheUpdater(..), initNameCache, knownKeyNames )\nimport Development.IDE.GHC.Compat\n ( mkSplitUniqSupply, upNameCache )\n" ▫︎ Perhaps: "import Development.IDE.GHC.Compat\n ( NameCache,\n NameCacheUpdater(..),\n initNameCache,\n knownKeyNames,\n mkSplitUniqSupply,\n upNameCache )\n"
NameCacheUpdater (..),
initNameCache,
knownKeyNames)
Expand Down Expand Up @@ -193,6 +203,7 @@
| LogCancelledAction !T.Text
| LogSessionInitialised
| LogLookupPersistentKey !T.Text
| LogRestartDebounceCount !Int !String
| LogShakeGarbageCollection !T.Text !Int !Seconds
-- * OfInterest Log messages
| LogSetFilesOfInterest ![(NormalizedFilePath, FileOfInterestStatus)]
Expand Down Expand Up @@ -239,6 +250,8 @@
LogSetFilesOfInterest ofInterest ->
"Set files of interst to" <> Pretty.line
<> indent 4 (pretty $ fmap (first fromNormalizedFilePath) ofInterest)
LogRestartDebounceCount count reason ->
"Restart debounce count:" <+> pretty count <+> ":" <+> pretty reason

-- | We need to serialize writes to the database, so we send any function that
-- needs to write to the database over the channel, where it will be picked up by
Expand All @@ -257,6 +270,10 @@
-- with (currently) retry functionality
type IndexQueue = TQueue (((HieDb -> IO ()) -> IO ()) -> IO ())

-- ShakeOpQueue is used to enqueue Shake operations.
-- shutdown, restart
type ShakeOpQueue = TQueue RestartArguments

-- Note [Semantic Tokens Cache Location]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- storing semantic tokens cache for each file in shakeExtras might
Expand Down Expand Up @@ -329,6 +346,7 @@
-- ^ Default HLS config, only relevant if the client does not provide any Config
, dirtyKeys :: TVar KeySet
-- ^ Set of dirty rule keys since the last Shake run
, shakeOpQueue :: ShakeOpQueue
}

type WithProgressFunc = forall a.
Expand Down Expand Up @@ -615,14 +633,15 @@
-> IdeTesting
-> WithHieDb
-> IndexQueue
-> ShakeOpQueue
-> ShakeOptions
-> Monitoring
-> Rules ()
-> IO IdeState
shakeOpen recorder lspEnv defaultConfig idePlugins debouncer
shakeProfileDir (IdeReportProgress reportProgress)
ideTesting@(IdeTesting testing)
withHieDb indexQueue opts monitoring rules = mdo
withHieDb indexQueue shakeOpQueue opts monitoring rules = mdo

#if MIN_VERSION_ghc(9,3,0)
ideNc <- initNameCache 'r' knownKeyNames
Expand Down Expand Up @@ -748,36 +767,110 @@
extras <- ask
liftIO $ shakeEnqueue extras a

data RestartArguments = RestartArguments
{ restartVFS :: VFSModified
, restartReasons :: [String]
, restartActions :: [DelayedAction ()]
, restartActionBetweenShakeSession :: [IO [Key]]
-- barrier to wait for the session stopped
, restartBarriers :: [Barrier ()]
, restartRecorder :: Recorder (WithPriority Log)
, restartIdeState :: IdeState
}

instance Semigroup RestartArguments where
RestartArguments a1 a2 a3 a4 a5 a6 _a7 <> RestartArguments b1 b2 b3 b4 b5 b6 b7 =
RestartArguments (a1 <> b1) (a2 <> b2) (a3 <> b3) (a4 <> b4) (a5 <> b5) b6 b7

-- do x until time up and do y
-- doUntil time out
doUntil :: IO a -> IO [a]
doUntil x = do
res <- x
rest <- doUntil x
return (res:rest)

runWithShake :: (ShakeOpQueue-> IO ()) -> IO ()
runWithShake f = do
stopQueue <- newTQueueIO
-- withAsync (stopShakeLoop stopQueue doQueue) $ const $
withAsync (runShakeLoop stopQueue) $
const $ f stopQueue
where
runShakeLoop :: ShakeOpQueue -> IO ()
runShakeLoop q = do
argHead <- atomically $ readTQueue q
-- sleep 0.1
-- args <- atomically $ flushTQueue q
case NE.nonEmpty (argHead:[]) of
Nothing -> return ()
Just xs -> do
let count = length xs
let arg = sconcat xs
let recorder = restartRecorder arg
logWith recorder Info $ LogRestartDebounceCount count (intercalate ", " (restartReasons arg))
doShakeRestart arg 0
runShakeLoop q

-- prepare the restart
stopShakeSession :: RestartArguments -> IO Seconds
stopShakeSession RestartArguments{restartIdeState=IdeState{..}, ..} = do
withMVarMasked shakeSession
(\runner -> do
(stopTime,()) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
-- signal the caller that we are done stopping and ready to restart
return stopTime
)
where
logErrorAfter :: Seconds -> IO () -> IO ()
logErrorAfter seconds action = flip withAsync (const action) $ do
sleep seconds
logWith restartRecorder Error (LogBuildSessionRestartTakingTooLong seconds)


doShakeRestart :: RestartArguments -> Seconds -> IO ()
doShakeRestart RestartArguments{restartIdeState=IdeState{..}, ..} stopTime = do
withMVar' shakeSession
(\runner -> do
(stopTime,()) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
keys <- concat <$> sequence restartActionBetweenShakeSession
mapM_ (flip signalBarrier ()) restartBarriers
atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \x -> foldl' (flip insertKeySet) x keys
res <- shakeDatabaseProfile shakeDb
backlog <- readTVarIO $ dirtyKeys shakeExtras
queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras
-- this log is required by tests
logWith restartRecorder Debug $ LogBuildSessionRestart (intercalate ", " restartReasons) queue backlog stopTime res
)
-- It is crucial to be masked here, otherwise we can get killed
-- between spawning the new thread and updating shakeSession.
-- See https://github.com/haskell/ghcide/issues/79
(\() -> do
(,()) <$> newSession restartRecorder shakeExtras restartVFS shakeDb restartActions (intercalate ", " restartReasons))
where
logErrorAfter :: Seconds -> IO () -> IO ()
logErrorAfter seconds action = flip withAsync (const action) $ do
sleep seconds
logWith restartRecorder Error (LogBuildSessionRestartTakingTooLong seconds)


-- | Restart the current 'ShakeSession' with the given system actions.
-- Any actions running in the current session will be aborted,
-- but actions added via 'shakeEnqueue' will be requeued.
shakeRestart :: Recorder (WithPriority Log) -> IdeState -> VFSModified -> String -> [DelayedAction ()] -> IO [Key] -> IO ()
shakeRestart recorder IdeState{..} vfs reason acts ioActionBetweenShakeSession =
withMVar'
shakeSession
(\runner -> do
(stopTime,()) <- duration $ logErrorAfter 10 $ cancelShakeSession runner
keys <- ioActionBetweenShakeSession
-- it is every important to update the dirty keys after we enter the critical section
-- see Note [Housekeeping rule cache and dirty key outside of hls-graph]
atomically $ modifyTVar' (dirtyKeys shakeExtras) $ \x -> foldl' (flip insertKeySet) x keys
res <- shakeDatabaseProfile shakeDb
backlog <- readTVarIO $ dirtyKeys shakeExtras
queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras

-- this log is required by tests
logWith recorder Debug $ LogBuildSessionRestart reason queue backlog stopTime res
)
-- It is crucial to be masked here, otherwise we can get killed
-- between spawning the new thread and updating shakeSession.
-- See https://github.com/haskell/ghcide/issues/79
(\() -> do
(,()) <$> newSession recorder shakeExtras vfs shakeDb acts reason)
where
logErrorAfter :: Seconds -> IO () -> IO ()
logErrorAfter seconds action = flip withAsync (const action) $ do
sleep seconds
logWith recorder Error (LogBuildSessionRestartTakingTooLong seconds)
shakeRestart recorder IdeState{..} vfs reason acts ioActionBetweenShakeSession = do
barrier <- newBarrier
let restartArgs = RestartArguments
{ restartVFS = vfs
, restartReasons = [reason]
, restartActions = acts
, restartActionBetweenShakeSession = [ioActionBetweenShakeSession]
, restartBarriers = [barrier]
, restartRecorder = recorder
, restartIdeState = IdeState{..}
}
atomically $ writeTQueue (shakeOpQueue $ shakeExtras) restartArgs
waitBarrier barrier

-- | Enqueue an action in the existing 'ShakeSession'.
-- Returns a computation to block until the action is run, propagating exceptions.
Expand All @@ -802,6 +895,9 @@
return (wait' b >>= either throwIO return)

data VFSModified = VFSUnmodified | VFSModified !VFS
instance Semigroup VFSModified where
VFSUnmodified <> x = x
x <> _ = x

-- | Set up a new 'ShakeSession' with a set of initial actions
-- Will crash if there is an existing 'ShakeSession' running.
Expand Down
14 changes: 8 additions & 6 deletions ghcide/src/Development/IDE/LSP/LanguageServer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import UnliftIO.Exception
import qualified Colog.Core as Colog
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Development.IDE.Core.IdeConfiguration
import Development.IDE.Core.Service (ShakeOpQueue,
runWithShake)
import Development.IDE.Core.Shake hiding (Log, Priority)
import Development.IDE.Core.Tracing
import qualified Development.IDE.Session as Session
Expand Down Expand Up @@ -128,7 +130,7 @@ setupLSP ::
Recorder (WithPriority Log)
-> (FilePath -> IO FilePath) -- ^ Map root paths to the location of the hiedb for the project
-> LSP.Handlers (ServerM config)
-> (LSP.LanguageContextEnv config -> Maybe FilePath -> WithHieDb -> IndexQueue -> IO IdeState)
-> (LSP.LanguageContextEnv config -> Maybe FilePath -> WithHieDb -> IndexQueue -> ShakeOpQueue -> IO IdeState)
-> MVar ()
-> IO (LSP.LanguageContextEnv config -> TRequestMessage Method_Initialize -> IO (Either err (LSP.LanguageContextEnv config, IdeState)),
LSP.Handlers (ServerM config),
Expand Down Expand Up @@ -186,7 +188,7 @@ setupLSP recorder getHieDbLoc userHandlers getIdeState clientMsgVar = do
handleInit
:: Recorder (WithPriority Log)
-> (FilePath -> IO FilePath)
-> (LSP.LanguageContextEnv config -> Maybe FilePath -> WithHieDb -> IndexQueue -> IO IdeState)
-> (LSP.LanguageContextEnv config -> Maybe FilePath -> WithHieDb -> IndexQueue -> ShakeOpQueue -> IO IdeState)
-> MVar ()
-> IO ()
-> (SomeLspId -> IO ())
Expand Down Expand Up @@ -228,8 +230,8 @@ handleInit recorder getHieDbLoc getIdeState lifetime exitClientMsg clearReqId wa
exceptionInHandler e
k $ ResponseError (InR ErrorCodes_InternalError) (T.pack $ show e) Nothing
_ <- flip forkFinally handleServerException $ do
untilMVar lifetime $ runWithDb (cmapWithPrio LogSession recorder) dbLoc $ \withHieDb' hieChan' -> do
putMVar dbMVar (WithHieDbShield withHieDb',hieChan')
untilMVar lifetime $ runWithShake $ \sq -> runWithDb (cmapWithPrio LogSession recorder) dbLoc $ \withHieDb' hieChan' -> do
putMVar dbMVar (WithHieDbShield withHieDb',hieChan',sq)
forever $ do
msg <- readChan clientMsgChan
-- We dispatch notifications synchronously and requests asynchronously
Expand All @@ -239,8 +241,8 @@ handleInit recorder getHieDbLoc getIdeState lifetime exitClientMsg clearReqId wa
ReactorRequest _id act k -> void $ async $ checkCancelled _id act k
logWith recorder Info LogReactorThreadStopped

(WithHieDbShield withHieDb,hieChan) <- takeMVar dbMVar
ide <- getIdeState env root withHieDb hieChan
(WithHieDbShield withHieDb,hieChan,sq) <- takeMVar dbMVar
ide <- getIdeState env root withHieDb hieChan sq
registerIdeConfiguration (shakeExtras ide) initConfig
pure $ Right (env,ide)

Expand Down
Loading
Loading