Skip to content

Commit 3cf7ceb

Browse files
committed
ensure we always send begin and end for progress
if startDelay and updateDelay are 0. It ensures that test cases relying on progress notifications to be sent are not flaky.
1 parent 966c65c commit 3cf7ceb

File tree

1 file changed

+50
-42
lines changed

1 file changed

+50
-42
lines changed

lsp/src/Language/LSP/Server/Progress.hs

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@ import Control.Monad
1717
import Control.Monad.IO.Class
1818
import Control.Monad.IO.Unlift
1919
import Data.Aeson qualified as J
20-
import Data.Foldable
2120
import Data.Map.Strict qualified as Map
2221
import Data.Maybe
2322
import Data.Text (Text)
23+
import Data.Text qualified as T
2424
import Language.LSP.Protocol.Lens qualified as L
2525
import Language.LSP.Protocol.Message
2626
import Language.LSP.Protocol.Types
2727
import Language.LSP.Protocol.Types qualified as L
2828
import Language.LSP.Server.Core
29-
import UnliftIO qualified as U
3029
import UnliftIO.Exception qualified as UE
3130

3231
{- | A package indicating the percentage of progress complete and a
@@ -53,16 +52,16 @@ instance E.Exception ProgressCancelledException
5352
data ProgressCancellable = Cancellable | NotCancellable
5453

5554
-- Get a new id for the progress session and make a new one
56-
getNewProgressId :: MonadLsp config m => m ProgressToken
57-
getNewProgressId = do
55+
getNewProgressId :: (MonadLsp config m) => Text -> m ProgressToken
56+
getNewProgressId title = do
5857
stateState (progressNextId . resProgressData) $ \cur ->
5958
let !next = cur + 1
60-
in (L.ProgressToken $ L.InL cur, next)
59+
in (L.ProgressToken $ L.InR (title <> T.pack (show cur)), next)
6160
{-# INLINE getNewProgressId #-}
6261

6362
withProgressBase ::
6463
forall c m a.
65-
MonadLsp c m =>
64+
(MonadLsp c m) =>
6665
Bool ->
6766
Text ->
6867
Maybe ProgressToken ->
@@ -102,12 +101,10 @@ withProgressBase indefinite title clientToken cancellable f = do
102101

103102
-- Deregister our 'ProgressToken', specifically its cancellation handler. It is important
104103
-- to do this reliably or else we will leak handlers.
105-
unregisterToken :: m ()
106-
unregisterToken = do
104+
unregisterToken :: ProgressToken -> m ()
105+
unregisterToken token = do
107106
handlers <- getProgressCancellationHandlers
108-
liftIO $ atomically $ do
109-
mt <- tryReadTMVar tokenVar
110-
for_ mt $ \t -> modifyTVar handlers (Map.delete t)
107+
liftIO $ atomically $ modifyTVar handlers (Map.delete token)
111108

112109
-- Find and register our 'ProgressToken', asking the client for it if necessary.
113110
-- Note that this computation may terminate before we get the token, we need to wait
@@ -120,14 +117,14 @@ withProgressBase indefinite title clientToken cancellable f = do
120117
-- the title/initial percentage aren't given until the 'begin' mesage. However,
121118
-- it's neater not to create tokens that we won't use, and clients may find it
122119
-- easier to clean them up if they receive begin/end reports for them.
123-
liftIO $ threadDelay startDelay
120+
when (startDelay > 0) $ liftIO $ threadDelay startDelay
124121
case clientToken of
125122
-- See Note [Client- versus server-initiated progress]
126123
-- Client-initiated progress
127124
Just t -> registerToken t
128125
-- Try server-initiated progress
129126
Nothing -> do
130-
t <- getNewProgressId
127+
t <- getNewProgressId title
131128
clientCaps <- getClientCapabilities
132129

133130
-- If we don't have a progress token from the client and
@@ -145,43 +142,54 @@ withProgressBase indefinite title clientToken cancellable f = do
145142
-- Successfully registered the token, we can now use it.
146143
-- So we go ahead and start. We do this as soon as we get the
147144
-- token back so the client gets feedback ASAP
148-
Right _ -> registerToken t
145+
Right _ -> do
146+
registerToken t
149147
-- The client sent us an error, we can't use the token.
150-
Left _err -> pure ()
151-
152-
-- Actually send the progress reports.
153-
sendReports :: m ()
154-
sendReports = do
155-
t <- liftIO $ atomically $ readTMVar tokenVar
156-
begin t
157-
-- Once we are sending updates, if we get interrupted we should send
158-
-- the end notification
159-
update t `UE.finally` end t
160-
where
161-
cancellable' = case cancellable of
162-
Cancellable -> Just True
163-
NotCancellable -> Just False
164-
begin t = do
148+
Left _err -> do
149+
pure ()
150+
151+
update t = do
152+
forever $ do
153+
-- See Note [Delayed progress reporting]
154+
when (updateDelay > 0) $ liftIO $ threadDelay updateDelay
165155
(ProgressAmount pct msg) <- liftIO $ atomically $ takeTMVar reportVar
166-
sendProgressReport t $ WorkDoneProgressBegin L.AString title cancellable' msg pct
167-
update t =
168-
forever $ do
169-
-- See Note [Delayed progress reporting]
170-
liftIO $ threadDelay updateDelay
171-
(ProgressAmount pct msg) <- liftIO $ atomically $ takeTMVar reportVar
172-
sendProgressReport t $ WorkDoneProgressReport L.AString Nothing msg pct
173-
end t = sendProgressReport t (WorkDoneProgressEnd L.AString Nothing)
156+
sendProgressReport t $ WorkDoneProgressReport L.AString Nothing msg pct
157+
end t = sendProgressReport t (WorkDoneProgressEnd L.AString Nothing)
158+
159+
begin t = do
160+
sendProgressReport t $ WorkDoneProgressBegin L.AString title cancellable' Nothing Nothing
161+
return t
162+
163+
cancellable' = case cancellable of
164+
Cancellable -> Just True
165+
NotCancellable -> Just False
166+
167+
-- if we have no delays then we can use uninterruptibleMask_ to create the token
168+
-- to ensure we always get begin and end messages
169+
maskTokenCreation =
170+
if startDelay == 0 && updateDelay == 0
171+
then UE.uninterruptibleMask_
172+
else id
174173

175174
-- Create the token and then start sending reports; all of which races with the check for the
176175
-- progress having ended. In all cases, make sure to unregister the token at the end.
177-
progressThreads :: m ()
178-
progressThreads =
179-
((createToken >> sendReports) `UE.finally` unregisterToken) `U.race_` liftIO progressEnded
180-
176+
progressThreads runInBase =
177+
runInBase
178+
( UE.bracket
179+
( maskTokenCreation $
180+
createToken
181+
>> liftIO (atomically $ readTMVar tokenVar)
182+
>>= begin
183+
)
184+
( \t -> end t >> unregisterToken t
185+
)
186+
update
187+
)
188+
`race_` progressEnded
181189
withRunInIO $ \runInBase -> do
182190
withAsync (runInBase $ f updater) $ \mainAct ->
183191
-- If the progress gets cancelled then we need to get cancelled too
184-
withAsync (runInBase progressThreads) $ \pthreads -> do
192+
withAsync (progressThreads runInBase) $ \pthreads -> do
185193
r <- waitEither mainAct pthreads
186194
-- TODO: is this weird? I can't see how else to gracefully use the ending barrier
187195
-- as a guard to cancel the other async

0 commit comments

Comments
 (0)