-
-
Notifications
You must be signed in to change notification settings - Fork 392
Progress reporting improvements #1784
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
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
a7aa9f0
factor out progress reporting
pepeiborra 583bb12
extract out progress reporting
pepeiborra 8f497d9
hlint
pepeiborra b8d0191
clean ups
pepeiborra 432f901
Fix splice plugin tests
pepeiborra cef8d9b
fix client settings test
pepeiborra 6e87c6c
Avoid empty report messages in the NoProgress style
pepeiborra ca9d893
avoid div by zero
pepeiborra 572b16f
wait for progress create response
pepeiborra 0b8c12d
simplify the outer loop away
pepeiborra 0dda71b
correctly implement progressStop
pepeiborra 6f83ff0
Improve asymptotics
pepeiborra 7f0c06d
Increase the parallelism used in hlint tests
pepeiborra 32de9de
extract recordProgress
pepeiborra 9c85641
comments
pepeiborra d5ebab3
fix test
pepeiborra ac0c0b9
remove unnecessary tilde
pepeiborra 3850495
apply review feedbacks
pepeiborra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
{-# LANGUAGE RankNTypes #-} | ||
module Development.IDE.Core.ProgressReporting | ||
( ProgressEvent(..) | ||
, ProgressReporting(..) | ||
, noProgressReporting | ||
, delayedProgressReporting | ||
-- utilities, reexported for use in Core.Shake | ||
, mRunLspT | ||
, mRunLspTCallback | ||
) | ||
where | ||
|
||
import Control.Concurrent.Async | ||
import Control.Concurrent.Strict | ||
import Control.Monad.Extra | ||
import Control.Monad.IO.Class | ||
import Control.Monad.Trans.Class (lift) | ||
import Data.Foldable (for_) | ||
import Data.Functor (($>)) | ||
import qualified Data.HashMap.Strict as HMap | ||
import Data.Maybe (isJust) | ||
import qualified Data.Text as T | ||
import Data.Unique | ||
import Development.IDE.GHC.Orphans () | ||
import Development.IDE.Graph hiding (ShakeValue) | ||
import Development.IDE.Types.Location | ||
import Development.IDE.Types.Options | ||
import qualified Language.LSP.Server as LSP | ||
import Language.LSP.Types | ||
import qualified Language.LSP.Types as LSP | ||
import System.Time.Extra | ||
import UnliftIO.Exception (bracket_) | ||
|
||
data ProgressEvent | ||
= KickStarted | ||
| KickCompleted | ||
|
||
data ProgressReporting = ProgressReporting | ||
{ progressUpdate :: ProgressEvent -> IO () | ||
, inProgress :: forall a. NormalizedFilePath -> Action a -> Action a | ||
, progressStop :: IO () | ||
} | ||
|
||
noProgressReporting :: IO ProgressReporting | ||
noProgressReporting = return $ ProgressReporting | ||
{ progressUpdate = const $ pure () | ||
, inProgress = const id | ||
, progressStop = pure () | ||
} | ||
|
||
-- | State used in 'delayedProgressReporting' | ||
data State | ||
= NotStarted | ||
| Stopped | ||
| Running (Async ()) | ||
|
||
-- | State transitions used in 'delayedProgressReporting' | ||
data Transition = Event ProgressEvent | StopProgress | ||
|
||
updateState :: IO () -> Transition -> State -> IO State | ||
updateState _ _ Stopped = pure Stopped | ||
updateState start (Event KickStarted) NotStarted = Running <$> async start | ||
updateState start (Event KickStarted) (Running a) = cancel a >> Running <$> async start | ||
updateState _ (Event KickCompleted) (Running a) = cancel a $> NotStarted | ||
updateState _ (Event KickCompleted) st = pure st | ||
updateState _ StopProgress (Running a) = cancel a $> Stopped | ||
updateState _ StopProgress st = pure st | ||
|
||
-- | Data structure to track progress across the project | ||
data InProgress = InProgress | ||
{ todo :: !Int -- ^ Number of files to do | ||
, done :: !Int -- ^ Number of files done | ||
, current :: !(HMap.HashMap NormalizedFilePath Int) | ||
} | ||
|
||
recordProgress :: NormalizedFilePath -> (Int -> Int) -> InProgress -> InProgress | ||
recordProgress file shift InProgress{..} = case HMap.alterF alter file current of | ||
((prev, new), m') -> | ||
let todo' = if isJust prev then todo else todo + 1 | ||
done' = if new == 0 then done+1 else done | ||
in InProgress todo' done' m' | ||
where | ||
alter x = let x' = maybe (shift 0) shift x in ((x,x'), Just x') | ||
|
||
-- | A 'ProgressReporting' that enqueues Begin and End notifications in a new | ||
-- thread, with a grace period (nothing will be sent if 'KickCompleted' arrives | ||
-- before the end of the grace period). | ||
delayedProgressReporting | ||
:: Seconds -- ^ Grace period before starting | ||
-> Seconds -- ^ sampling delay | ||
-> Maybe (LSP.LanguageContextEnv c) | ||
-> ProgressReportingStyle | ||
-> IO ProgressReporting | ||
delayedProgressReporting before after lspEnv optProgressStyle = do | ||
inProgressVar <- newVar $ InProgress 0 0 mempty | ||
progressState <- newVar NotStarted | ||
let progressUpdate event = updateStateVar $ Event event | ||
progressStop = updateStateVar StopProgress | ||
updateStateVar = modifyVar_ progressState . updateState (mRunLspT lspEnv $ lspShakeProgress inProgressVar) | ||
|
||
inProgress :: NormalizedFilePath -> Action a -> Action a | ||
inProgress = withProgressVar inProgressVar | ||
return ProgressReporting{..} | ||
where | ||
lspShakeProgress inProgress = do | ||
-- first sleep a bit, so we only show progress messages if it's going to take | ||
-- a "noticable amount of time" (we often expect a thread kill to arrive before the sleep finishes) | ||
liftIO $ sleep before | ||
u <- ProgressTextToken . T.pack . show . hashUnique <$> liftIO newUnique | ||
|
||
b <- liftIO newBarrier | ||
void $ LSP.sendRequest LSP.SWindowWorkDoneProgressCreate | ||
LSP.WorkDoneProgressCreateParams { _token = u } $ liftIO . signalBarrier b | ||
ready <- liftIO $ waitBarrier b | ||
|
||
for_ ready $ const $ bracket_ (start u) (stop u) (loop u 0) | ||
where | ||
start id = LSP.sendNotification LSP.SProgress $ | ||
LSP.ProgressParams | ||
{ _token = id | ||
, _value = LSP.Begin $ WorkDoneProgressBeginParams | ||
{ _title = "Processing" | ||
, _cancellable = Nothing | ||
, _message = Nothing | ||
, _percentage = Nothing | ||
} | ||
} | ||
stop id = LSP.sendNotification LSP.SProgress | ||
LSP.ProgressParams | ||
{ _token = id | ||
, _value = LSP.End WorkDoneProgressEndParams | ||
{ _message = Nothing | ||
} | ||
} | ||
loop _ _ | optProgressStyle == NoProgress = | ||
forever $ liftIO $ threadDelay maxBound | ||
loop id prev = do | ||
InProgress{..} <- liftIO $ readVar inProgress | ||
liftIO $ sleep after | ||
if todo == 0 then loop id 0 else do | ||
pepeiborra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let next = 100 * fromIntegral done / fromIntegral todo | ||
when (next /= prev) $ | ||
LSP.sendNotification LSP.SProgress $ | ||
LSP.ProgressParams | ||
{ _token = id | ||
, _value = LSP.Report $ case optProgressStyle of | ||
Explicit -> LSP.WorkDoneProgressReportParams | ||
{ _cancellable = Nothing | ||
, _message = Just $ T.pack $ show done <> "/" <> show todo | ||
, _percentage = Nothing | ||
} | ||
Percentage -> LSP.WorkDoneProgressReportParams | ||
{ _cancellable = Nothing | ||
, _message = Nothing | ||
, _percentage = Just next | ||
} | ||
NoProgress -> error "unreachable" | ||
} | ||
loop id next | ||
|
||
withProgressVar var file = actionBracket (f succ) (const $ f pred) . const | ||
-- This functions are deliberately eta-expanded to avoid space leaks. | ||
-- Do not remove the eta-expansion without profiling a session with at | ||
-- least 1000 modifications. | ||
where | ||
f shift = modifyVar' var $ recordProgress file shift | ||
|
||
mRunLspT :: Applicative m => Maybe (LSP.LanguageContextEnv c ) -> LSP.LspT c m () -> m () | ||
mRunLspT (Just lspEnv) f = LSP.runLspT lspEnv f | ||
mRunLspT Nothing _ = pure () | ||
|
||
mRunLspTCallback :: Monad m | ||
=> Maybe (LSP.LanguageContextEnv c) | ||
-> (LSP.LspT c m a -> LSP.LspT c m a) | ||
-> m a | ||
-> m a | ||
mRunLspTCallback (Just lspEnv) f g = LSP.runLspT lspEnv $ f (lift g) | ||
mRunLspTCallback Nothing _ g = g |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.