Skip to content

Commit e65065a

Browse files
committed
Remove stateful interface
1 parent b81f25c commit e65065a

File tree

1 file changed

+63
-87
lines changed

1 file changed

+63
-87
lines changed

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

Lines changed: 63 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
{-# LANGUAGE OverloadedStrings #-}
77
{-# LANGUAGE RoleAnnotations #-}
88
{-# LANGUAGE TypeFamilyDependencies #-}
9-
{-# LANGUAGE NumericUnderscores #-}
109
{-# LANGUAGE UndecidableInstances #-}
1110
{-# LANGUAGE CUSKs #-}
1211
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
@@ -66,6 +65,7 @@ import Language.LSP.Protocol.Utils.SMethodMap qualified as SMethodMap
6665
import Language.LSP.VFS hiding (end)
6766
import Prettyprinter
6867
import System.Random hiding (next)
68+
import UnliftIO qualified as U
6969
import UnliftIO.Exception qualified as UE
7070

7171
-- ---------------------------------------------------------------------
@@ -244,21 +244,25 @@ data VFSData = VFSData
244244
{-# INLINE modifyState #-}
245245
modifyState :: MonadLsp config m => (LanguageContextState config -> TVar a) -> (a -> a) -> m ()
246246
modifyState sel f = do
247-
tvarDat <- sel . resState <$> getLspEnv
247+
tvarDat <- getStateVar sel
248248
liftIO $ atomically $ modifyTVar' tvarDat f
249249

250250
{-# INLINE stateState #-}
251251
stateState :: MonadLsp config m => (LanguageContextState config -> TVar s) -> (s -> (a, s)) -> m a
252252
stateState sel f = do
253-
tvarDat <- sel . resState <$> getLspEnv
253+
tvarDat <- getStateVar sel
254254
liftIO $ atomically $ stateTVar tvarDat f
255255

256256
{-# INLINE getsState #-}
257257
getsState :: MonadLsp config m => (LanguageContextState config -> TVar a) -> m a
258258
getsState f = do
259-
tvarDat <- f . resState <$> getLspEnv
259+
tvarDat <- getStateVar f
260260
liftIO $ readTVarIO tvarDat
261261

262+
{-# INLINE getStateVar #-}
263+
getStateVar :: MonadLsp config m => (LanguageContextState config -> TVar a) -> m (TVar a)
264+
getStateVar f = f . resState <$> getLspEnv
265+
262266
-- ---------------------------------------------------------------------
263267

264268
{- | Options that the server may configure.
@@ -313,8 +317,8 @@ instance Default Options where
313317
Nothing
314318
False
315319
-- See Note [Delayed progress reporting]
316-
1_000_000
317-
5_00_000
320+
0
321+
0
318322

319323
defaultOptions :: Options
320324
defaultOptions = def
@@ -645,14 +649,6 @@ unregisterCapability (RegistrationToken m (RegistrationId uuid)) = do
645649
-- PROGRESS
646650
--------------------------------------------------------------------------------
647651

648-
addProgressCancellationHandler :: MonadLsp config m => ProgressToken -> IO () -> m ()
649-
addProgressCancellationHandler n act = modifyState (progressCancel . resProgressData) $ Map.insert n act
650-
{-# INLINE addProgressCancellationHandler #-}
651-
652-
deleteProgressCancellationHandler :: MonadLsp config m => ProgressToken -> m ()
653-
deleteProgressCancellationHandler n = modifyState (progressCancel . resProgressData) $ Map.delete n
654-
{-# INLINE deleteProgressCancellationHandler #-}
655-
656652
-- Get a new id for the progress session and make a new one
657653
getNewProgressId :: MonadLsp config m => m ProgressToken
658654
getNewProgressId = do
@@ -673,44 +669,56 @@ data ProgressTracker = ProgressTracker
673669
-- set it when it finishes the work.
674670
}
675671

676-
-- | Create a 'ProgressTracker'.
677-
makeProgressTracker ::
678-
forall c m.
672+
withProgressBase ::
673+
forall c m a.
679674
MonadLsp c m =>
675+
Bool ->
680676
Text ->
681-
ProgressAmount ->
682677
Maybe ProgressToken ->
683678
ProgressCancellable ->
684-
m ProgressTracker
685-
makeProgressTracker title initialProgress clientToken cancellable = do
679+
((ProgressAmount -> m ()) -> m a) ->
680+
m a
681+
withProgressBase indefinite title clientToken cancellable f = do
682+
let initialProgress = ProgressAmount (if indefinite then Nothing else Just 0) Nothing
686683
LanguageContextEnv{resProgressStartDelay = startDelay, resProgressUpdateDelay = updateDelay} <- getLspEnv
687684

688685
tokenVar <- liftIO newEmptyTMVarIO
689686
reportVar <- liftIO $ newTMVarIO initialProgress
690687
endBarrier <- liftIO newEmptyMVar
691688

692689
let
693-
sendProgressReport :: (J.ToJSON r) => ProgressToken -> r -> m ()
694-
sendProgressReport token report = sendNotification SMethod_Progress $ ProgressParams token $ J.toJSON report
690+
updater :: ProgressAmount -> m ()
691+
updater pa = liftIO $ atomically $ do
692+
-- I don't know of a way to do this with a normal MVar!
693+
-- That is: put something into it regardless of whether it is full or empty
694+
_ <- tryTakeTMVar reportVar
695+
putTMVar reportVar pa
696+
697+
progressEnded :: IO ()
698+
progressEnded = readMVar endBarrier
695699

696-
-- \| Once we have a 'ProgressToken', store it in the variable and also register the cancellation
700+
endProgress :: IO ()
701+
endProgress = void $ tryPutMVar endBarrier ()
702+
703+
-- Once we have a 'ProgressToken', store it in the variable and also register the cancellation
697704
-- handler.
698705
registerToken :: ProgressToken -> m ()
699706
registerToken t = do
700-
-- TODO: this is currently racy, we need these two to occur in one STM
701-
-- transaction
702-
liftIO $ atomically $ putTMVar tokenVar t
703-
addProgressCancellationHandler t (void $ tryPutMVar endBarrier ())
707+
handlers <- getProgressCancellationHandlers
708+
liftIO $ atomically $ do
709+
putTMVar tokenVar t
710+
modifyTVar handlers (Map.insert t endProgress)
704711

705-
-- \| Deregister our 'ProgressToken', specifically its cancellation handler. It is important
712+
-- Deregister our 'ProgressToken', specifically its cancellation handler. It is important
706713
-- to do this reliably or else we will leak handlers.
707714
unregisterToken :: m ()
708715
unregisterToken = do
709-
-- TODO: this is also racy, see above
710-
t <- liftIO $ atomically $ tryReadTMVar tokenVar
711-
for_ t deleteProgressCancellationHandler
716+
handlers <- getProgressCancellationHandlers
717+
liftIO $ atomically $ do
718+
mt <- tryReadTMVar tokenVar
719+
for_ mt $ \t -> modifyTVar handlers (Map.delete t)
712720

713-
-- \| Find and register our 'ProgressToken', asking the client for it if necessary.
721+
-- Find and register our 'ProgressToken', asking the client for it if necessary.
714722
-- Note that this computation may terminate before we get the token, we need to wait
715723
-- for the token var to be filled if we want to use it.
716724
createToken :: m ()
@@ -743,7 +751,7 @@ makeProgressTracker title initialProgress clientToken cancellable = do
743751
-- The client sent us an error, we can't use the token.
744752
Left _err -> pure ()
745753

746-
-- \| Actually send the progress reports.
754+
-- Actually send the progress reports.
747755
sendReports :: m ()
748756
sendReports = do
749757
t <- liftIO $ atomically $ readTMVar tokenVar
@@ -771,54 +779,28 @@ makeProgressTracker title initialProgress clientToken cancellable = do
771779
sendProgressReport t $ WorkDoneProgressReport L.AString Nothing msg pct
772780
end t = sendProgressReport t (WorkDoneProgressEnd L.AString Nothing)
773781

774-
-- \| Blocks until the progress reporting should end.
775-
endProgress :: IO ()
776-
endProgress = readMVar endBarrier
777-
778-
progressThreads :: m (Async ())
779-
progressThreads = withRunInIO $ \runInBase ->
780-
async $
781-
-- Create the token and then start sending reports; all of which races with the check for the
782-
-- progress having ended. In all cases, make sure to unregister the token at the end.
783-
(runInBase (createToken >> sendReports) `race_` endProgress) `E.finally` runInBase unregisterToken
784-
785-
-- Launch the threads with no handle, rely on the end barrier to kill them
786-
_threads <- progressThreads
787-
788-
-- The update function for clients: just write to the var
789-
let update pa = atomically $ do
790-
-- I don't know of a way to do this with a normal MVar!
791-
-- That is: put something into it regardless of whether it is full or empty
792-
_ <- tryTakeTMVar reportVar
793-
putTMVar reportVar pa
794-
pure $ ProgressTracker update endBarrier
782+
-- Create the token and then start sending reports; all of which races with the check for the
783+
-- progress having ended. In all cases, make sure to unregister the token at the end.
784+
progressThreads :: m ()
785+
progressThreads =
786+
((createToken >> sendReports) `UE.finally` unregisterToken) `U.race_` liftIO progressEnded
787+
788+
withRunInIO $ \runInBase -> do
789+
withAsync (runInBase $ f updater) $ \mainAct ->
790+
-- If the progress gets cancelled then we need to get cancelled too
791+
withAsync (runInBase progressThreads) $ \pthreads -> do
792+
r <- waitEither mainAct pthreads
793+
-- TODO: is this weird? I can't see how else to gracefully use the ending barrier
794+
-- as a guard to cancel the other async
795+
case r of
796+
Left a -> pure a
797+
Right _ -> cancelWith mainAct ProgressCancelledException >> wait mainAct
798+
where
799+
sendProgressReport :: (J.ToJSON r) => ProgressToken -> r -> m ()
800+
sendProgressReport token report = sendNotification SMethod_Progress $ ProgressParams token $ J.toJSON report
795801

796-
withProgressBase ::
797-
forall c m a.
798-
MonadLsp c m =>
799-
Bool ->
800-
Text ->
801-
Maybe ProgressToken ->
802-
ProgressCancellable ->
803-
((ProgressAmount -> m ()) -> m a) ->
804-
m a
805-
withProgressBase indefinite title clientToken cancellable f = withRunInIO $ \runInBase -> do
806-
let initialPercentage = if indefinite then Nothing else Just 0
807-
E.bracket
808-
-- Create the progress tracker, which will start the progress threads
809-
(runInBase $ makeProgressTracker title (ProgressAmount initialPercentage Nothing) clientToken cancellable)
810-
-- When we finish, trigger the progress ending barrier
811-
(\tracker -> tryPutMVar (progressEnded tracker) ())
812-
$ \tracker -> do
813-
-- Tie the given computation to the progress ending barrier so it will cancel us if triggered
814-
withAsync (runInBase $ f (liftIO . updateProgress tracker)) $ \mainAct ->
815-
withAsync (readMVar (progressEnded tracker)) $ \ender -> do
816-
-- TODO: is this weird? I can't see how else to gracefully use the ending barrier
817-
-- as a guard to cancel the other async
818-
r <- waitEither mainAct ender
819-
case r of
820-
Left a -> pure a
821-
Right _ -> cancelWith mainAct ProgressCancelledException >> wait mainAct
802+
getProgressCancellationHandlers :: m (TVar (Map.Map ProgressToken (IO ())))
803+
getProgressCancellationHandlers = getStateVar (progressCancel . resProgressData)
822804

823805
clientSupportsServerInitiatedProgress :: L.ClientCapabilities -> Bool
824806
clientSupportsServerInitiatedProgress caps = fromMaybe False $ caps ^? L.window . _Just . L.workDoneProgress . _Just
@@ -1050,13 +1032,7 @@ like the client's job. Nonetheless, this does not always happen, and so it is he
10501032
to moderate the spam.
10511033
10521034
For this reason we have configurable delays on starting progress tracking and on sending
1053-
updates.
1054-
1055-
The default values we use are based on the usual interface responsiveness research:
1056-
- 1s is about the point at which people definitely notice something is happening, so
1057-
this is where we start progress reporting.
1058-
- Updates are at 0.5s, so they happen fast enough that things are clearly happening,
1059-
without being too distracting.
1035+
updates. However, the defaults are set to 0, so it's opt-in.
10601036
-}
10611037

10621038
{- Note [Request cancellation]

0 commit comments

Comments
 (0)