Skip to content

Commit 5e7ee92

Browse files
committed
[fix] don't throw hard errors when no shutdown message is handled
Previously, when there was no shutdown message by a client and the client disconnected, resulting in the handlers to be GC'd the race that was supposed to free resources for the HieDB & co. would throw a hard error talking about the MVar being unreachable. We would like to instead finish gracefully because finishing the race as soon as the MVar was GC'd is the right thing to do anyway.
1 parent 43e8cb0 commit 5e7ee92

File tree

1 file changed

+17
-10
lines changed

1 file changed

+17
-10
lines changed

ghcide/src/Development/IDE/LSP/LanguageServer.hs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import UnliftIO.Directory
3434
import UnliftIO.Exception
3535

3636
import qualified Colog.Core as Colog
37+
import Control.Exception (BlockedIndefinitelyOnMVar (..))
3738
import Control.Monad.IO.Unlift (MonadUnliftIO)
3839
import Control.Monad.Trans.Cont (evalContT)
3940
import Development.IDE.Core.IdeConfiguration
@@ -94,14 +95,14 @@ runLanguageServer
9495
-> (MVar ()
9596
-> IO (LSP.LanguageContextEnv config -> TRequestMessage Method_Initialize -> IO (Either (TResponseError Method_Initialize) (LSP.LanguageContextEnv config, a)),
9697
LSP.Handlers (m config),
97-
(LanguageContextEnv config, a) -> m config <~> IO))
98+
(LanguageContextEnv config, a) -> m config <~> IO, [IO ()]))
9899
-> IO ()
99100
runLanguageServer recorder options inH outH defaultConfig parseConfig onConfigChange setup = do
100101
-- This MVar becomes full when the server thread exits or we receive exit message from client.
101102
-- LSP server will be canceled when it's full.
102103
clientMsgVar <- newEmptyMVar
103104

104-
(doInitialize, staticHandlers, interpretHandler) <- setup clientMsgVar
105+
(doInitialize, staticHandlers, interpretHandler, onExit) <- setup clientMsgVar
105106

106107
let serverDefinition = LSP.ServerDefinition
107108
{ LSP.parseConfig = parseConfig
@@ -118,13 +119,14 @@ runLanguageServer recorder options inH outH defaultConfig parseConfig onConfigCh
118119
let lspCologAction :: MonadIO m2 => Colog.LogAction m2 (Colog.WithSeverity LspServerLog)
119120
lspCologAction = toCologActionWithPrio (cmapWithPrio LogLspServer recorder)
120121

121-
void $ untilMVar clientMsgVar $
122-
void $ LSP.runServerWithHandles
122+
untilMVar clientMsgVar $
123+
LSP.runServerWithHandles
123124
lspCologAction
124125
lspCologAction
125126
inH
126127
outH
127128
serverDefinition
129+
`finally` sequence_ onExit
128130

129131
setupLSP ::
130132
forall config err.
@@ -136,7 +138,8 @@ setupLSP ::
136138
-> MVar ()
137139
-> IO (LSP.LanguageContextEnv config -> TRequestMessage Method_Initialize -> IO (Either err (LSP.LanguageContextEnv config, IdeState)),
138140
LSP.Handlers (ServerM config),
139-
(LanguageContextEnv config, IdeState) -> ServerM config <~> IO)
141+
(LanguageContextEnv config, IdeState) -> ServerM config <~> IO,
142+
[IO ()])
140143
setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar = do
141144
-- Send everything over a channel, since you need to wait until after initialise before
142145
-- LspFuncs is available
@@ -184,7 +187,9 @@ setupLSP recorder defaultRoot getHieDbLoc userHandlers getIdeState clientMsgVar
184187

185188
let interpretHandler (env, st) = LSP.Iso (LSP.runLspT env . flip (runReaderT . unServerM) (clientMsgChan,st)) liftIO
186189

187-
pure (doInitialize, asyncHandlers, interpretHandler)
190+
let finalHandlers = [stopReactorLoop, exit]
191+
192+
pure (doInitialize, asyncHandlers, interpretHandler, finalHandlers)
188193

189194

190195
handleInit
@@ -265,11 +270,13 @@ runWithWorkerThreads recorder dbLoc f = evalContT $ do
265270
(WithHieDbShield hiedb, threadQueue) <- runWithDb recorder dbLoc
266271
liftIO $ f hiedb (ThreadQueue threadQueue sessionRestartTQueue sessionLoaderTQueue)
267272

268-
-- | Runs the action until it ends or until the given MVar is put.
273+
-- | Runs the action until it ends or until the given MVar is put or the thread to fill the mvar is dropped, in which case the MVar will never be filled.
274+
-- This happens when the thread that handles the shutdown notification dies. Ideally, this should not rely on the RTS detecting the blocked MVar
275+
-- and instead *also* run the shutdown inf a finally block enclosing the handlers. In which case the BlockedIndefinitelyOnMVar Exception also wouldn't
276+
-- be thrown.
269277
-- Rethrows any exceptions.
270-
untilMVar :: MonadUnliftIO m => MVar () -> m () -> m ()
271-
untilMVar mvar io = void $
272-
waitAnyCancel =<< traverse async [ io , readMVar mvar ]
278+
untilMVar :: MonadUnliftIO m => MVar () -> m a -> m ()
279+
untilMVar mvar io = race_ (readMVar mvar) io
273280

274281
cancelHandler :: (SomeLspId -> IO ()) -> LSP.Handlers (ServerM c)
275282
cancelHandler cancelRequest = LSP.notificationHandler SMethod_CancelRequest $ \TNotificationMessage{_params=CancelParams{_id}} ->

0 commit comments

Comments
 (0)