, aeson
, aeson-pretty
, ansi-terminal
+ , async
, bytestring
, conduit
, conduit-parse
import Data.Aeson
import Data.Default
import qualified Data.HashMap.Strict as HashMap
+import Data.IORef
import qualified Data.Map as Map
import Data.Maybe
import Language.Haskell.LSP.Types
-> Session a -- ^ The session to run.
-> IO a
runSessionWithConfig config serverExe caps rootDir session = do
+ -- We use this IORef to make exception non-fatal when the server is supposed to shutdown.
+ exitOk <- newIORef False
pid <- getCurrentProcessID
absRootDir <- canonicalizePath rootDir
(Just TraceOff)
Nothing
withServer serverExe (logStdErr config) $ \serverIn serverOut _ ->
- runSessionWithHandles serverIn serverOut listenServer config caps rootDir $ do
+ runSessionWithHandles serverIn serverOut (\h c -> catchWhenTrue exitOk $ listenServer h c) config caps rootDir $ do
-- Wrap the session around initialize and shutdown calls
initRspMsg <- request Initialize initializeParams :: Session InitializeResponse
-- Run the actual test
result <- session
+ liftIO $ atomicWriteIORef exitOk True
sendNotification Exit ExitParams
return result
where
+ catchWhenTrue :: IORef Bool -> IO () -> IO ()
+ catchWhenTrue exitOk a =
+ a `catch` (\e -> do
+ x <- readIORef exitOk
+ unless x $ throw (e :: SomeException))
+
-- | Listens to the server output, makes sure it matches the record and
-- signals any semaphores
+ -- Note that on Windows, we cannot kill a thread stuck in getNextMessage.
+ -- So we have to wait for the exit notification to kill the process first
+ -- and then getNextMessage will fail.
listenServer :: Handle -> SessionContext -> IO ()
listenServer serverOut context = do
msgBytes <- getNextMessage serverOut
module Language.Haskell.LSP.Test.Server (withServer) where
-import Control.Concurrent
-import Control.Exception
+import Control.Concurrent.Async
import Control.Monad
import Language.Haskell.LSP.Test.Compat
import System.IO
-- separate command and arguments
let cmd:args = words serverExe
createProc = (proc cmd args) { std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe }
- (Just serverIn, Just serverOut, Just serverErr, serverProc) <- createProcess createProc
-
+ withCreateProcess createProc $ \(Just serverIn) (Just serverOut) (Just serverErr) serverProc -> do
-- Need to continuously consume to stderr else it gets blocked
-- Can't pass NoStream either to std_err
hSetBuffering serverErr NoBuffering
- errSinkThread <- forkIO $ forever $ hGetLine serverErr >>= when logStdErr . putStrLn
-
+ hSetBinaryMode serverErr True
+ let errSinkThread = forever $ hGetLine serverErr >>= when logStdErr . putStrLn
+ withAsync errSinkThread $ \_ -> do
pid <- getProcessID serverProc
-
- finally (f serverIn serverOut pid) $ do
- killThread errSinkThread
- terminateProcess serverProc
+ f serverIn serverOut pid
hSetBuffering serverIn NoBuffering
hSetBuffering serverOut NoBuffering
+ -- This is required to make sure that we don’t get any
+ -- newline conversion or weird encoding issues.
+ hSetBinaryMode serverIn True
+ hSetBinaryMode serverOut True
reqMap <- newMVar newRequestMap
messageChan <- newChan