Merge pull request #39 from cocreature/fix-windows
authorLuke Lau <luke_lau@icloud.com>
Sat, 8 Jun 2019 19:00:08 +0000 (20:00 +0100)
committerGitHub <noreply@github.com>
Sat, 8 Jun 2019 19:00:08 +0000 (20:00 +0100)
Fix various issues encountered on Windows

lsp-test.cabal
src/Language/Haskell/LSP/Test.hs
src/Language/Haskell/LSP/Test/Server.hs
src/Language/Haskell/LSP/Test/Session.hs

index aca12b0a92e86b2cd904c31573fe71ccd4e479b4..f1035b2572d38ed8bbb416cb9f1a6d51b9c5f96f 100644 (file)
@@ -40,6 +40,7 @@ library
                      , aeson
                      , aeson-pretty
                      , ansi-terminal
+                     , async
                      , bytestring
                      , conduit
                      , conduit-parse
index 15cb2a164d4600530cbc6a626f8fedbfee7890f9..bc04845f8d9774e4f5cd00c48e8cbe74153e1417 100644 (file)
@@ -92,6 +92,7 @@ import qualified Data.Text.IO as T
 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
@@ -135,6 +136,8 @@ runSessionWithConfig :: SessionConfig -- ^ Configuration options for the session
                      -> 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
 
@@ -146,7 +149,7 @@ runSessionWithConfig config serverExe caps rootDir session = do
                                           (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
@@ -165,12 +168,22 @@ runSessionWithConfig config serverExe caps rootDir session = do
       -- 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
index bd5bdb959f670652a5e910d5d0942b43fcb0c494..5449dfbbf40317aae94ba65a17ef9d169dc08a6b 100644 (file)
@@ -1,7 +1,6 @@
 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
@@ -13,15 +12,12 @@ withServer serverExe logStdErr f = do
   -- 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
index a3ba35b3a1a46f723d4ca0fce59775991680c862..ab09726f2ef5490654d4a41099ed53e42f03b608 100644 (file)
@@ -197,6 +197,10 @@ runSessionWithHandles serverIn serverOut serverHandler config caps rootDir sessi
 
   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