Skip to content

Commit 6ee37a0

Browse files
committed
ensure we always send begin and end for progress if startDelay and updateDelay are 0
1 parent 966c65c commit 6ee37a0

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)