Skip to content

Commit c1f4c7f

Browse files
Merge branch 'master' into ghc-9.2
2 parents 6782d55 + d1e6d3c commit c1f4c7f

19 files changed

+201
-90
lines changed

Diff for: .github/workflows/bench.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
uses: fkirc/[email protected]
2525
with:
2626
cancel_others: false
27-
paths_ignore: '["**/docs/**", "**.md", "**/LICENSE", ".circleci/**", "install/**", "**.nix", "**/test/**", "flake.lock", "**/README.md", "FUNDING.yml"]'
27+
paths_ignore: '["**/docs/**", "**.md", "**/LICENSE", ".circleci/**", "install/**", "**.nix", "**/test/**", "flake.lock", "**/README.md", "FUNDING.yml", "**/stack*.yaml"]'
2828

2929
bench_init:
3030
if: needs.pre_job.outputs.should_skip != 'true'

Diff for: .github/workflows/caching.yml

+21-4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ on:
3030
push:
3131
branches:
3232
- master
33+
schedule:
34+
# Refresh snapshot every (02+8*x):25 UTC
35+
# When cache is present it is a light check workflow with early termination.
36+
# When primary cache is not hit - runs the cache generation.
37+
# Why: GitHub repo has 10G pool & on overflow GitHub removes caches in FIFO manner.
38+
# When internal branche PRs save into the same pool -
39+
# their cache is accessible only inside of the scope of the PR.
40+
# If main cache is forced out - there are no cache shared between PRs,
41+
# which implies all PRs would start to create & save their cache.
42+
# Reinstitution of the main chache puts it back into FIFO
43+
# & so it gets shared across all PRs.
44+
- cron: "25 2/8 * * *"
3345

3446
env:
3547
cabalBuild: "v2-build all --enable-tests --enable-benchmarks"
@@ -78,7 +90,7 @@ jobs:
7890
- if: runner.os == 'Windows'
7991
name: (Windows) Platform config
8092
run: |
81-
echo "CABAL_PKGS_DIR=~\\AppData\\cabal\\packages" >> $GITHUB_ENV
93+
echo "CABAL_PKGS_DIR=C:\\cabal\\packages" >> $GITHUB_ENV
8294
- if: ( runner.os == 'Linux' ) || ( runner.os == 'macOS' )
8395
name: (Linux,macOS) Platform config
8496
run: |
@@ -130,6 +142,7 @@ jobs:
130142
restore-keys: ${{ env.cache-name }}-
131143

132144
- name: Compiled deps cache
145+
id: compiled-deps
133146
uses: actions/cache@v2
134147
env:
135148
cache-name: compiled-deps
@@ -141,9 +154,12 @@ jobs:
141154
${{ env.cache-name }}-${{ runner.os }}-${{ matrix.ghc }}-
142155
${{ env.cache-name }}-${{ runner.os }}-
143156
144-
- run: cabal update
157+
- if: (! steps.compiled-deps.outputs.cache-hit)
158+
run: |
159+
cabal update
145160
146-
- name: Download all sources
161+
- if: (! steps.compiled-deps.outputs.cache-hit)
162+
name: Download all sources
147163
run: |
148164
cabal $cabalBuild --only-download
149165
@@ -152,7 +168,8 @@ jobs:
152168
# but to cache what can be cached, so step is fault tolerant & would always succseed.
153169
# 2021-12-11: NOTE: Building all targets, since
154170
# current Cabal does not allow `all --enable-tests --enable-benchmarks --only-dependencies`
155-
- name: Build all targets; try 3 times
171+
- if: (! steps.compiled-deps.outputs.cache-hit)
172+
name: Build all targets; try 3 times
156173
continue-on-error: true
157174
run: |
158175
cabal $cabalBuild || cabal $cabalBuild || cabal $cabalBuild

Diff for: .github/workflows/nix.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
uses: fkirc/[email protected]
2525
with:
2626
cancel_others: false
27-
paths_ignore: '["**/docs/**", "**.md", "**/LICENSE", ".circleci/**", "install/**", "**/README.md", "FUNDING.yml"]'
27+
paths_ignore: '["**/docs/**", "**.md", "**/LICENSE", ".circleci/**", "install/**", "**/README.md", "FUNDING.yml", "**/stack*.yaml"]'
2828
- id: skip_check_no_nix
2929
uses: fkirc/[email protected]
3030
with:

Diff for: .github/workflows/test.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
uses: fkirc/[email protected]
2626
with:
2727
cancel_others: false
28-
paths_ignore: '["**/docs/**", "**.md", "**/LICENSE", "install/**", "**.nix", "flake.lock", "**/README.md", "FUNDING.yml", ".circleci/**"]'
28+
paths_ignore: '["**/docs/**", "**.md", "**/LICENSE", "install/**", "**.nix", "flake.lock", "**/README.md", "FUNDING.yml", ".circleci/**", "**/stack*.yaml"]'
2929
# If we only change ghcide downstream packages we have not test ghcide itself
3030
- id: skip_ghcide_check
3131
uses: fkirc/[email protected]
@@ -85,7 +85,7 @@ jobs:
8585
- if: runner.os == 'Windows'
8686
name: (Windows) Platform config
8787
run: |
88-
echo "CABAL_PKGS_DIR=~\\AppData\\cabal\\packages" >> $GITHUB_ENV
88+
echo "CABAL_PKGS_DIR=C:\\cabal\\packages" >> $GITHUB_ENV
8989
- if: ( runner.os == 'Linux' ) || ( runner.os == 'macOS' )
9090
name: (Linux,macOS) Platform config
9191
run: |

Diff for: cabal-ghc901.project

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ package *
3737

3838
write-ghc-environment-files: never
3939

40-
index-state: 2021-11-29T12:30:07Z
40+
index-state: 2021-11-29T12:30:08Z
4141

4242
constraints:
4343
-- These plugins don't work on GHC9 yet

Diff for: cabal.project

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ package *
4040

4141
write-ghc-environment-files: never
4242

43-
index-state: 2021-11-29T12:30:07Z
43+
index-state: 2021-11-29T12:30:08Z
4444

4545
constraints:
4646
hyphenation +embed

Diff for: ghcide/ghcide.cabal

+3-1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ library
146146
Development.IDE
147147
Development.IDE.Main
148148
Development.IDE.Core.Actions
149+
Development.IDE.Main.HeapStats
149150
Development.IDE.Core.Debouncer
150151
Development.IDE.Core.FileStore
151152
Development.IDE.Core.IdeConfiguration
@@ -300,7 +301,8 @@ executable ghcide
300301
-rtsopts
301302
-- disable idle GC
302303
-- increase nursery size
303-
"-with-rtsopts=-I0 -A128M"
304+
-- Enable collection of heap statistics
305+
"-with-rtsopts=-I0 -A128M -T"
304306
main-is: Main.hs
305307
build-depends:
306308
hiedb,

Diff for: ghcide/src/Development/IDE/Core/Rules.hs

-3
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,6 @@ import Ide.Types (DynFlagsModificat
149149
PluginId)
150150
import Control.Concurrent.STM.Stats (atomically)
151151
import Language.LSP.Server (LspT)
152-
import System.Environment (getExecutablePath)
153-
import System.Process.Extra (readProcessWithExitCode)
154-
import Text.Read (readMaybe)
155152
import System.Info.Extra (isMac)
156153
import HIE.Bios.Ghc.Gap (hostIsDynamic)
157154

Diff for: ghcide/src/Development/IDE/Core/Shake.hs

+7-5
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ import Data.Aeson (toJSON)
154154
import qualified Data.ByteString.Char8 as BS8
155155
import Data.Coerce (coerce)
156156
import Data.Default
157-
import Data.Foldable (toList)
157+
import Data.Foldable (for_, toList)
158158
import Data.HashSet (HashSet)
159159
import qualified Data.HashSet as HSet
160160
import Data.String (fromString)
@@ -455,7 +455,7 @@ recordDirtyKeys
455455
recordDirtyKeys ShakeExtras{dirtyKeys} key file = do
456456
modifyTVar' dirtyKeys $ \x -> foldl' (flip HSet.insert) x (toKey key <$> file)
457457
return $ withEventTrace "recordDirtyKeys" $ \addEvent -> do
458-
addEvent (fromString $ "dirty " <> show key) (fromString $ unlines $ map fromNormalizedFilePath file)
458+
addEvent (fromString $ unlines $ "dirty " <> show key : map fromNormalizedFilePath file)
459459

460460
-- | We return Nothing if the rule has not run and Just Failed if it has failed to produce a value.
461461
getValues ::
@@ -583,15 +583,17 @@ startTelemetry db extras@ShakeExtras{..}
583583

584584
-- | Must be called in the 'Initialized' handler and only once
585585
shakeSessionInit :: IdeState -> IO ()
586-
shakeSessionInit IdeState{..} = do
586+
shakeSessionInit ide@IdeState{..} = do
587587
initSession <- newSession shakeExtras shakeDb [] "shakeSessionInit"
588588
putMVar shakeSession initSession
589+
logDebug (ideLogger ide) "Shake session initialized"
589590

590591
shakeShut :: IdeState -> IO ()
591-
shakeShut IdeState{..} = withMVar shakeSession $ \runner -> do
592+
shakeShut IdeState{..} = do
593+
runner <- tryReadMVar shakeSession
592594
-- Shake gets unhappy if you try to close when there is a running
593595
-- request so we first abort that.
594-
void $ cancelShakeSession runner
596+
for_ runner cancelShakeSession
595597
void $ shakeDatabaseProfile shakeDb
596598
shakeClose
597599
progressStop $ progress shakeExtras

Diff for: ghcide/src/Development/IDE/Core/Tracing.hs

+4-4
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ import qualified StmContainers.Map as STM
7171
#if MIN_VERSION_ghc(8,8,0)
7272
otTracedProvider :: MonadUnliftIO m => PluginId -> ByteString -> m a -> m a
7373
otTracedGarbageCollection :: (MonadMask f, MonadIO f, Show a) => ByteString -> f [a] -> f [a]
74-
withEventTrace :: (MonadMask m, MonadIO m) => String -> ((ByteString -> ByteString -> m ()) -> m a) -> m a
74+
withEventTrace :: (MonadMask m, MonadIO m) => String -> ((ByteString -> m ()) -> m a) -> m a
7575
#else
7676
otTracedProvider :: MonadUnliftIO m => PluginId -> String -> m a -> m a
7777
otTracedGarbageCollection :: (MonadMask f, MonadIO f, Show a) => String -> f [a] -> f [a]
78-
withEventTrace :: (MonadMask m, MonadIO m) => String -> ((String -> ByteString -> m ()) -> m a) -> m a
78+
withEventTrace :: (MonadMask m, MonadIO m) => String -> ((ByteString -> m ()) -> m a) -> m a
7979
#endif
8080

8181
withTrace :: (MonadMask m, MonadIO m) =>
@@ -90,8 +90,8 @@ withTrace name act
9090
withEventTrace name act
9191
| userTracingEnabled
9292
= withSpan (fromString name) $ \sp -> do
93-
act (addEvent sp)
94-
| otherwise = act (\_ _ -> pure ())
93+
act (addEvent sp "")
94+
| otherwise = act (\_ -> pure ())
9595

9696
-- | Returns a logger that produces telemetry events in a single span
9797
withTelemetryLogger :: (MonadIO m, MonadMask m) => (Logger -> m a) -> m a

Diff for: ghcide/src/Development/IDE/LSP/LanguageServer.hs

+71-49
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ import Development.IDE.Core.Tracing
3838
import Development.IDE.LSP.HoverDefinition
3939
import Development.IDE.Types.Logger
4040

41+
import Control.Monad.IO.Unlift (MonadUnliftIO)
4142
import System.IO.Unsafe (unsafeInterleaveIO)
4243

44+
issueTrackerUrl :: T.Text
45+
issueTrackerUrl = "https://github.com/haskell/haskell-language-server/issues"
46+
4347
runLanguageServer
4448
:: forall config. (Show config)
4549
=> LSP.Options
@@ -54,11 +58,16 @@ runLanguageServer
5458
runLanguageServer options inH outH getHieDbLoc defaultConfig onConfigurationChange userHandlers getIdeState = do
5559

5660
-- This MVar becomes full when the server thread exits or we receive exit message from client.
57-
-- LSP loop will be canceled when it's full.
61+
-- LSP server will be canceled when it's full.
5862
clientMsgVar <- newEmptyMVar
5963
-- Forcefully exit
6064
let exit = void $ tryPutMVar clientMsgVar ()
6165

66+
-- An MVar to control the lifetime of the reactor loop.
67+
-- The loop will be stopped and resources freed when it's full
68+
reactorLifetime <- newEmptyMVar
69+
let stopReactorLoop = void $ tryPutMVar reactorLifetime ()
70+
6271
-- The set of requests ids that we have received but not finished processing
6372
pendingRequests <- newTVarIO Set.empty
6473
-- The set of requests that have been cancelled and are also in pendingRequests
@@ -93,7 +102,7 @@ runLanguageServer options inH outH getHieDbLoc defaultConfig onConfigurationChan
93102
[ ideHandlers
94103
, cancelHandler cancelRequest
95104
, exitHandler exit
96-
, shutdownHandler
105+
, shutdownHandler stopReactorLoop
97106
]
98107
-- Cancel requests are special since they need to be handled
99108
-- out of order to be useful. Existing handlers are run afterwards.
@@ -102,25 +111,23 @@ runLanguageServer options inH outH getHieDbLoc defaultConfig onConfigurationChan
102111
let serverDefinition = LSP.ServerDefinition
103112
{ LSP.onConfigurationChange = onConfigurationChange
104113
, LSP.defaultConfig = defaultConfig
105-
, LSP.doInitialize = handleInit exit clearReqId waitForCancel clientMsgChan
114+
, LSP.doInitialize = handleInit reactorLifetime exit clearReqId waitForCancel clientMsgChan
106115
, LSP.staticHandlers = asyncHandlers
107116
, LSP.interpretHandler = \(env, st) -> LSP.Iso (LSP.runLspT env . flip runReaderT (clientMsgChan,st)) liftIO
108117
, LSP.options = modifyOptions options
109118
}
110119

111-
void $ waitAnyCancel =<< traverse async
112-
[ void $ LSP.runServerWithHandles
120+
void $ untilMVar clientMsgVar $
121+
void $ LSP.runServerWithHandles
113122
inH
114123
outH
115124
serverDefinition
116-
, void $ readMVar clientMsgVar
117-
]
118125

119126
where
120127
handleInit
121-
:: IO () -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> Chan ReactorMessage
128+
:: MVar () -> IO () -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> Chan ReactorMessage
122129
-> LSP.LanguageContextEnv config -> RequestMessage Initialize -> IO (Either err (LSP.LanguageContextEnv config, IdeState))
123-
handleInit exitClientMsg clearReqId waitForCancel clientMsgChan env (RequestMessage _ _ m params) = otTracedHandler "Initialize" (show m) $ \sp -> do
130+
handleInit lifetime exitClientMsg clearReqId waitForCancel clientMsgChan env (RequestMessage _ _ m params) = otTracedHandler "Initialize" (show m) $ \sp -> do
124131
traceWithSpan sp params
125132
let root = LSP.resRootPath env
126133
dir <- maybe getCurrentDirectory return root
@@ -138,58 +145,73 @@ runLanguageServer options inH outH getHieDbLoc defaultConfig onConfigurationChan
138145
registerIdeConfiguration (shakeExtras ide) initConfig
139146

140147
let handleServerException (Left e) = do
141-
logError (ideLogger ide) $
148+
logError logger $
142149
T.pack $ "Fatal error in server thread: " <> show e
150+
sendErrorMessage e
143151
exitClientMsg
144-
handleServerException _ = pure ()
152+
handleServerException (Right _) = pure ()
153+
154+
sendErrorMessage (e :: SomeException) = do
155+
LSP.runLspT env $ LSP.sendNotification SWindowShowMessage $
156+
ShowMessageParams MtError $ T.unlines
157+
[ "Unhandled exception, please [report](" <> issueTrackerUrl <> "): "
158+
, T.pack(show e)
159+
]
160+
161+
exceptionInHandler e = do
162+
logError logger $ T.pack $
163+
"Unexpected exception, please report!\n" ++
164+
"Exception: " ++ show e
165+
sendErrorMessage e
166+
145167
logger = ideLogger ide
146-
_ <- flip forkFinally handleServerException $ runWithDb logger dbLoc $ \hiedb hieChan -> do
147-
putMVar dbMVar (hiedb,hieChan)
148-
forever $ do
149-
msg <- readChan clientMsgChan
150-
-- We dispatch notifications synchronously and requests asynchronously
151-
-- This is to ensure that all file edits and config changes are applied before a request is handled
152-
case msg of
153-
ReactorNotification act -> do
154-
catch act $ \(e :: SomeException) ->
155-
logError (ideLogger ide) $ T.pack $
156-
"Unexpected exception on notification, please report!\n" ++
157-
"Exception: " ++ show e
158-
ReactorRequest _id act k -> void $ async $
159-
checkCancelled ide clearReqId waitForCancel _id act k
168+
169+
checkCancelled _id act k =
170+
flip finally (clearReqId _id) $
171+
catch (do
172+
-- We could optimize this by first checking if the id
173+
-- is in the cancelled set. However, this is unlikely to be a
174+
-- bottleneck and the additional check might hide
175+
-- issues with async exceptions that need to be fixed.
176+
cancelOrRes <- race (waitForCancel _id) act
177+
case cancelOrRes of
178+
Left () -> do
179+
logDebug (ideLogger ide) $ T.pack $ "Cancelled request " <> show _id
180+
k $ ResponseError RequestCancelled "" Nothing
181+
Right res -> pure res
182+
) $ \(e :: SomeException) -> do
183+
exceptionInHandler e
184+
k $ ResponseError InternalError (T.pack $ show e) Nothing
185+
_ <- flip forkFinally handleServerException $ do
186+
untilMVar lifetime $ runWithDb logger dbLoc $ \hiedb hieChan -> do
187+
putMVar dbMVar (hiedb,hieChan)
188+
forever $ do
189+
msg <- readChan clientMsgChan
190+
-- We dispatch notifications synchronously and requests asynchronously
191+
-- This is to ensure that all file edits and config changes are applied before a request is handled
192+
case msg of
193+
ReactorNotification act -> handle exceptionInHandler act
194+
ReactorRequest _id act k -> void $ async $ checkCancelled _id act k
195+
logInfo logger "Reactor thread stopped"
160196
pure $ Right (env,ide)
161197

162-
checkCancelled
163-
:: IdeState -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> SomeLspId
164-
-> IO () -> (ResponseError -> IO ()) -> IO ()
165-
checkCancelled ide clearReqId waitForCancel _id act k =
166-
flip finally (clearReqId _id) $
167-
catch (do
168-
-- We could optimize this by first checking if the id
169-
-- is in the cancelled set. However, this is unlikely to be a
170-
-- bottleneck and the additional check might hide
171-
-- issues with async exceptions that need to be fixed.
172-
cancelOrRes <- race (waitForCancel _id) act
173-
case cancelOrRes of
174-
Left () -> do
175-
logDebug (ideLogger ide) $ T.pack $ "Cancelled request " <> show _id
176-
k $ ResponseError RequestCancelled "" Nothing
177-
Right res -> pure res
178-
) $ \(e :: SomeException) -> do
179-
logError (ideLogger ide) $ T.pack $
180-
"Unexpected exception on request, please report!\n" ++
181-
"Exception: " ++ show e
182-
k $ ResponseError InternalError (T.pack $ show e) Nothing
183198

199+
-- | Runs the action until it ends or until the given MVar is put.
200+
-- Rethrows any exceptions.
201+
untilMVar :: MonadUnliftIO m => MVar () -> m () -> m ()
202+
untilMVar mvar io = void $
203+
waitAnyCancel =<< traverse async [ io , readMVar mvar ]
184204

185205
cancelHandler :: (SomeLspId -> IO ()) -> LSP.Handlers (ServerM c)
186206
cancelHandler cancelRequest = LSP.notificationHandler SCancelRequest $ \NotificationMessage{_params=CancelParams{_id}} ->
187207
liftIO $ cancelRequest (SomeLspId _id)
188208

189-
shutdownHandler :: LSP.Handlers (ServerM c)
190-
shutdownHandler = LSP.requestHandler SShutdown $ \_ resp -> do
209+
shutdownHandler :: IO () -> LSP.Handlers (ServerM c)
210+
shutdownHandler stopReactor = LSP.requestHandler SShutdown $ \_ resp -> do
191211
(_, ide) <- ask
192-
liftIO $ logDebug (ideLogger ide) "Received exit message"
212+
liftIO $ logDebug (ideLogger ide) "Received shutdown message"
213+
-- stop the reactor to free up the hiedb connection
214+
liftIO stopReactor
193215
-- flush out the Shake session to record a Shake profile if applicable
194216
liftIO $ shakeShut ide
195217
resp $ Right Empty

0 commit comments

Comments
 (0)