Skip to content

Commit 545ed44

Browse files
committed
migrate to classy concurrency
1 parent cc382b5 commit 545ed44

File tree

4 files changed

+256
-52
lines changed

4 files changed

+256
-52
lines changed

ghcide/ghcide.cabal

+24
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ library
4141
binary,
4242
bytestring,
4343
case-insensitive,
44+
concurrency,
4445
containers,
4546
data-default,
4647
deepseq,
4748
directory,
4849
dependent-map,
4950
dependent-sum,
5051
dlist,
52+
exceptions,
5153
extra >= 1.7.4,
5254
fuzzy,
5355
filepath,
@@ -309,6 +311,28 @@ executable ghcide
309311
TypeApplications
310312
ViewPatterns
311313

314+
test-suite concurrency-tests
315+
type: exitcode-stdio-1.0
316+
default-language: Haskell2010
317+
build-depends:
318+
async,
319+
base,
320+
concurrency,
321+
dejafu,
322+
exceptions,
323+
extra,
324+
filepath,
325+
ghcide,
326+
lsp-types,
327+
tasty,
328+
tasty-dejafu,
329+
tasty-hunit,
330+
text,
331+
transformers
332+
hs-source-dirs: test/conc
333+
ghc-options: -threaded
334+
main-is: Main.hs
335+
312336
test-suite ghcide-tests
313337
type: exitcode-stdio-1.0
314338
default-language: Haskell2010

ghcide/src/Development/IDE/Core/ProgressReporting.hs

+87-49
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
{-# LANGUAGE RankNTypes #-}
1+
{-# LANGUAGE ConstraintKinds #-}
2+
{-# LANGUAGE FlexibleInstances #-}
3+
{-# LANGUAGE FunctionalDependencies #-}
4+
{-# LANGUAGE MultiParamTypeClasses #-}
5+
{-# LANGUAGE PolyKinds #-}
6+
{-# LANGUAGE QuantifiedConstraints #-}
7+
{-# LANGUAGE RankNTypes #-}
8+
{-# LANGUAGE ScopedTypeVariables #-}
29
module Development.IDE.Core.ProgressReporting
310
( ProgressEvent(..)
411
, ProgressReporting(..)
@@ -10,57 +17,63 @@ module Development.IDE.Core.ProgressReporting
1017
-- for tests
1118
, recordProgress
1219
, InProgress(..)
20+
, MonadLSP(..)
21+
, MonadProgress(..)
22+
, MonadUnique(..)
1323
)
1424
where
1525

16-
import Control.Concurrent.Async
17-
import Control.Concurrent.Strict
26+
import Control.Concurrent.Classy
27+
import Control.Concurrent.Classy.Async
28+
import Control.Exception (evaluate)
29+
import Control.Monad.Catch (bracket_, finally)
1830
import Control.Monad.Extra
19-
import Control.Monad.IO.Class
20-
import Control.Monad.Trans.Class (lift)
21-
import Data.Foldable (for_)
22-
import Data.Functor (($>))
23-
import qualified Data.HashMap.Strict as HMap
24-
import qualified Data.Text as T
25-
import Data.Tuple.Extra (dupe)
26-
import Data.Unique
27-
import Development.IDE.GHC.Orphans ()
28-
import Development.IDE.Graph hiding (ShakeValue)
31+
import Control.Monad.Trans
32+
import Data.Bifunctor (bimap)
33+
import Data.Foldable (for_)
34+
import Data.Functor (($>))
35+
import qualified Data.HashMap.Strict as HMap
36+
import qualified Data.Text as T
37+
import Data.Tuple.Extra (dupe)
38+
import Data.Unique (hashUnique)
39+
import qualified Data.Unique as IO
40+
import Development.IDE.GHC.Orphans ()
41+
import Development.IDE.Graph (Action, actionBracket)
2942
import Development.IDE.Types.Location
3043
import Development.IDE.Types.Options
31-
import qualified Language.LSP.Server as LSP
44+
import qualified Language.LSP.Server as LSP
3245
import Language.LSP.Types
33-
import qualified Language.LSP.Types as LSP
46+
import qualified Language.LSP.Types as LSP
3447
import System.Time.Extra
35-
import UnliftIO.Exception (bracket_, evaluate)
48+
import UnliftIO (MonadUnliftIO)
3649

3750
data ProgressEvent
3851
= KickStarted
3952
| KickCompleted
4053

41-
data ProgressReporting = ProgressReporting
42-
{ progressUpdate :: ProgressEvent -> IO ()
43-
, inProgress :: forall a. NormalizedFilePath -> Action a -> Action a
44-
, progressStop :: IO ()
54+
data ProgressReporting io m = ProgressReporting
55+
{ progressUpdate :: ProgressEvent -> io ()
56+
, inProgress :: forall a. NormalizedFilePath -> m a -> m a
57+
, progressStop :: io ()
4558
}
4659

47-
noProgressReporting :: IO ProgressReporting
60+
noProgressReporting :: Monad m => m (ProgressReporting m n)
4861
noProgressReporting = return $ ProgressReporting
4962
{ progressUpdate = const $ pure ()
5063
, inProgress = const id
5164
, progressStop = pure ()
5265
}
5366

5467
-- | State used in 'delayedProgressReporting'
55-
data State
68+
data State m
5669
= NotStarted
5770
| Stopped
58-
| Running (Async ())
71+
| Running (Async m ())
5972

6073
-- | State transitions used in 'delayedProgressReporting'
6174
data Transition = Event ProgressEvent | StopProgress
6275

63-
updateState :: IO () -> Transition -> State -> IO State
76+
updateState :: MonadConc m => m () -> Transition -> State m -> m (State m)
6477
updateState _ _ Stopped = pure Stopped
6578
updateState start (Event KickStarted) NotStarted = Running <$> async start
6679
updateState start (Event KickStarted) (Running a) = cancel a >> Running <$> async start
@@ -91,40 +104,58 @@ recordProgress file shift InProgress{..} = case HMap.alterF alter file current o
91104
where
92105
alter x = let x' = maybe (shift 0) shift x in ((x,x'), Just x')
93106

107+
class (forall n . Functor n => Functor (m n)) => MonadLSP c m | m -> c where
108+
sendNotification :: forall n (meth :: Method 'FromServer 'Notification) . c n => SServerMethod meth -> MessageParams meth -> m n ()
109+
sendRequest :: forall n (meth :: Method 'FromServer 'Request) . c n => SServerMethod meth -> MessageParams meth -> (Either () () -> n ()) -> m n ()
110+
111+
instance MonadLSP MonadUnliftIO (LSP.LspT c) where
112+
sendNotification m p = LSP.sendNotification m p
113+
sendRequest m p k = void $ LSP.sendRequest m p (lift . k . bimap (const ()) (const ()))
114+
115+
class MonadUnique m where newUnique :: m Int
116+
instance MonadUnique IO where newUnique = hashUnique <$> IO.newUnique
117+
94118
-- | A 'ProgressReporting' that enqueues Begin and End notifications in a new
95119
-- thread, with a grace period (nothing will be sent if 'KickCompleted' arrives
96120
-- before the end of the grace period).
97121
delayedProgressReporting
98-
:: Seconds -- ^ Grace period before starting
122+
:: forall c lsp m action
123+
. (c m, MonadProgress m action, MonadConc m, MonadUnique m, MonadLSP c lsp)
124+
=> Seconds -- ^ Grace period before starting
99125
-> Seconds -- ^ sampling delay
100-
-> Maybe (LSP.LanguageContextEnv c)
101126
-> ProgressReportingStyle
102-
-> IO ProgressReporting
103-
delayedProgressReporting before after lspEnv optProgressStyle = do
127+
-> (lsp m () -> m ())
128+
-> m (ProgressReporting m action)
129+
delayedProgressReporting before after optProgressStyle runLsp = do
104130
inProgressVar <- newMVar $ InProgress 0 0 mempty
105131
progressState <- newMVar NotStarted
106132
let progressUpdate event = updateStateVar $ Event event
107-
progressStop = updateStateVar StopProgress
108-
updateStateVar = modifyMVar_ progressState . updateState (mRunLspT lspEnv $ lspShakeProgress inProgressVar)
133+
progressStop = updateStateVar StopProgress
134+
updateStateVar = modifyMVar_ progressState . updateState (lspShakeProgress inProgressVar)
109135

110-
inProgress :: NormalizedFilePath -> Action a -> Action a
136+
-- inProgress :: NormalizedFilePath -> m a -> m a
111137
inProgress = withProgressVar inProgressVar
112138
return ProgressReporting{..}
113139
where
140+
lspShakeProgress :: MVar m InProgress -> m ()
114141
lspShakeProgress inProgress = do
115142
-- first sleep a bit, so we only show progress messages if it's going to take
116143
-- a "noticable amount of time" (we often expect a thread kill to arrive before the sleep finishes)
117-
liftIO $ sleep before
118-
u <- ProgressTextToken . T.pack . show . hashUnique <$> liftIO newUnique
119-
120-
b <- liftIO newEmptyMVar
121-
void $ LSP.sendRequest LSP.SWindowWorkDoneProgressCreate
122-
LSP.WorkDoneProgressCreateParams { _token = u } $ liftIO . putMVar b
123-
ready <- liftIO $ takeMVar b
124-
125-
for_ ready $ const $ bracket_ (start u) (stop u) (loop u 0)
144+
-- threadDelay (floor $ before * 1e9)
145+
u <- ProgressTextToken . T.pack . show <$> newUnique
146+
147+
b <- newEmptyMVar
148+
runLsp $ sendRequest LSP.SWindowWorkDoneProgressCreate
149+
LSP.WorkDoneProgressCreateParams { _token = u } (putMVar b)
150+
ready <- takeMVar b
151+
152+
for_ ready $ const $ uninterruptibleMask $ \unmask -> do
153+
start u
154+
-- stop u
155+
unmask (loop u 0) `finally` stop u
156+
-- bracket_ (start u) (stop u) (loop u 0)
126157
where
127-
start id = LSP.sendNotification LSP.SProgress $
158+
start id = runLsp $ sendNotification LSP.SProgress $
128159
LSP.ProgressParams
129160
{ _token = id
130161
, _value = LSP.Begin $ WorkDoneProgressBeginParams
@@ -134,22 +165,22 @@ delayedProgressReporting before after lspEnv optProgressStyle = do
134165
, _percentage = Nothing
135166
}
136167
}
137-
stop id = LSP.sendNotification LSP.SProgress
168+
stop id = runLsp $ sendNotification LSP.SProgress
138169
LSP.ProgressParams
139170
{ _token = id
140171
, _value = LSP.End WorkDoneProgressEndParams
141172
{ _message = Nothing
142173
}
143174
}
144175
loop _ _ | optProgressStyle == NoProgress =
145-
forever $ liftIO $ threadDelay maxBound
176+
forever $ threadDelay maxBound
146177
loop id prev = do
147-
InProgress{..} <- liftIO $ readMVar inProgress
148-
liftIO $ sleep after
178+
InProgress{..} <- readMVar inProgress
179+
threadDelay (floor $ after * 1e9)
149180
if todo == 0 then loop id 0 else do
150181
let next = 100 * fromIntegral done / fromIntegral todo
151-
when (next /= prev) $
152-
LSP.sendNotification LSP.SProgress $
182+
when (next /= prev) $ runLsp $
183+
sendNotification LSP.SProgress $
153184
LSP.ProgressParams
154185
{ _token = id
155186
, _value = LSP.Report $ case optProgressStyle of
@@ -167,12 +198,19 @@ delayedProgressReporting before after lspEnv optProgressStyle = do
167198
}
168199
loop id next
169200

170-
withProgressVar var file = actionBracket (f succ) (const $ f pred) . const
201+
withProgressVar var file = withProgressV var (f succ) (f pred)
171202
-- This functions are deliberately eta-expanded to avoid space leaks.
172203
-- Do not remove the eta-expansion without profiling a session with at
173204
-- least 1000 modifications.
174205
where
175-
f shift = modifyMVar var $ evaluate . dupe . recordProgress file shift
206+
f shift = recordProgress file shift
207+
208+
class MonadProgress io m | m -> io where
209+
withProgressV :: MVar io a -> (a->a) -> (a->a) -> m b -> m b
210+
211+
instance MonadProgress IO Action where
212+
withProgressV var succ pred =
213+
actionBracket (modifyMVar var (evaluate.dupe.succ)) (const $ modifyMVar var (evaluate.dupe.pred)) . const
176214

177215
mRunLspT :: Applicative m => Maybe (LSP.LanguageContextEnv c ) -> LSP.LspT c m () -> m ()
178216
mRunLspT (Just lspEnv) f = LSP.runLspT lspEnv f

ghcide/src/Development/IDE/Core/Shake.hs

+8-3
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,12 @@ import Data.Vector (Vector)
107107
import qualified Data.Vector as Vector
108108
import Development.IDE.Core.Debouncer
109109
import Development.IDE.Core.PositionMapping
110-
import Development.IDE.Core.ProgressReporting
110+
import Development.IDE.Core.ProgressReporting (ProgressEvent (..),
111+
ProgressReporting (..),
112+
delayedProgressReporting,
113+
mRunLspT,
114+
mRunLspTCallback,
115+
noProgressReporting)
111116
import Development.IDE.Core.RuleTypes
112117
import Development.IDE.Core.Tracing
113118
import Development.IDE.GHC.Compat (NameCacheUpdater (..),
@@ -192,7 +197,7 @@ data ShakeExtras = ShakeExtras
192197
-- positions in a version of that document to positions in the latest version
193198
-- First mapping is delta from previous version and second one is an
194199
-- accumlation of all previous mappings.
195-
,progress :: ProgressReporting
200+
,progress :: ProgressReporting IO Action
196201
,ideTesting :: IdeTesting
197202
-- ^ Whether to enable additional lsp messages used by the test suite for checking invariants
198203
,restartShakeSession
@@ -519,7 +524,7 @@ shakeOpen lspEnv defaultConfig logger debouncer
519524
progress <- do
520525
let (before, after) = if testing then (0,0.1) else (0.1,0.1)
521526
if reportProgress
522-
then delayedProgressReporting before after lspEnv optProgressStyle
527+
then delayedProgressReporting before after optProgressStyle (mRunLspT lspEnv)
523528
else noProgressReporting
524529
actionQueue <- newQueue
525530

0 commit comments

Comments
 (0)