1515{-# LANGUAGE NamedFieldPuns #-}
1616{-# LANGUAGE DeriveGeneric #-}
1717{-# LANGUAGE LambdaCase #-}
18+ {-# LANGUAGE ViewPatterns #-}
1819----------------------------------------------------------------------------
1920module DAP.Server
2021 ( runDAPServer
2122 , runDAPServerWithLogger
2223 , readPayload
24+ , TerminateServer (.. )
2325 ) where
2426----------------------------------------------------------------------------
2527import Control.Monad ( when , forever )
28+ import Control.Concurrent ( ThreadId , myThreadId , throwTo )
2629import Control.Concurrent.MVar ( newMVar )
2730import Control.Concurrent.STM ( newTVarIO )
28- import Control.Exception ( SomeException
31+ import Control.Exception ( Exception
32+ , SomeAsyncException (.. )
33+ , SomeException
2934 , IOException
3035 , catch
3136 , fromException
37+ , toException
3238 , throwIO )
3339import Control.Monad ( void )
3440import Data.Aeson ( decodeStrict , eitherDecode , Value , FromJSON )
@@ -42,6 +48,7 @@ import System.IO ( hClose, hSetNewlineMode, Handle, N
4248 , NewlineMode (NewlineMode , outputNL , inputNL )
4349 , IOMode (ReadWriteMode ), stderr , hPrint )
4450import System.IO.Error ( isEOFError )
51+ import System.Exit ( exitWith , ExitCode (ExitSuccess ) )
4552import Text.Read ( readMaybe )
4653import qualified Data.ByteString.Lazy.Char8 as BL8
4754import qualified Data.ByteString.Char8 as BS
@@ -63,7 +70,11 @@ stdoutLogger = do
6370 withLock handleLock $ do
6471 T. putStrLn msg
6572
66-
73+ -- | An exception to throw if you want to stop the server execution from a
74+ -- client. This is useful if you launch a new server per debugging session and
75+ -- want to terminate it at the end.
76+ data TerminateServer = TerminateServer
77+ deriving (Show , Exception )
6778
6879runDAPServer :: ServerConfig -> (Command -> Adaptor app Request () ) -> IO ()
6980runDAPServer config communicate = do
@@ -81,13 +92,19 @@ runDAPServerWithLogger rawLogAction serverConfig@ServerConfig {..} communicate =
8192 let logAction = cfilter (\ msg -> if debugLogging then True else severity msg /= DEBUG ) rawLogAction
8293 logAction <& (mkDebugMessage $ (T. pack (" Running DAP server on " <> show port <> " ..." )))
8394 appStore <- newTVarIO mempty
84- serve (Host host) (show port) $ \ (socket, address) -> do
85- logAction <& mkDebugMessage (T. pack (" TCP connection established from " ++ show address))
86- handle <- socketToHandle socket ReadWriteMode
87- hSetNewlineMode handle NewlineMode { inputNL = CRLF , outputNL = CRLF }
88- adaptorStateMVar <- initAdaptorState logAction handle address appStore serverConfig
89- serviceClient communicate adaptorStateMVar
90- `catch` exceptionHandler logAction handle address debugLogging
95+ mainThread <- myThreadId
96+ let
97+ server = serve (Host host) (show port) $ \ (socket, address) -> do
98+ logAction <& mkDebugMessage (T. pack (" TCP connection established from " ++ show address))
99+ handle <- socketToHandle socket ReadWriteMode
100+ hSetNewlineMode handle NewlineMode { inputNL = CRLF , outputNL = CRLF }
101+ adaptorStateMVar <- initAdaptorState logAction handle address appStore serverConfig
102+ serviceClient communicate adaptorStateMVar
103+ `catch` exceptionHandler logAction handle address debugLogging mainThread
104+ server `catch` \ (SomeAsyncException e) ->
105+ case fromException $ toException e of
106+ Just TerminateServer -> exitWith ExitSuccess
107+ _ -> throwIO e
91108
92109-- | Initializes the Adaptor
93110--
@@ -120,11 +137,18 @@ serviceClient communicate lcl = forever $ runAdaptorWith lcl st $ do
120137 where
121138 st = AdaptorState MessageTypeResponse []
122139----------------------------------------------------------------------------
123- -- | Handle exceptions from client threads, parse and log accordingly
124- exceptionHandler :: LogAction IO DAPLog -> Handle -> SockAddr -> Bool -> SomeException -> IO ()
125- exceptionHandler logAction handle address shouldLog (e :: SomeException ) = do
140+ -- | Handle exceptions from client threads, parse and log accordingly.
141+ -- Detects if client failed with `TerminateServer` and kills the server accordingly by sending an exception to the main thread.
142+ exceptionHandler :: LogAction IO DAPLog -> Handle -> SockAddr -> Bool -> ThreadId -> SomeException -> IO ()
143+ exceptionHandler logAction handle address shouldLog serverThread (e :: SomeException ) = do
126144 let
127145 dumpError
146+ | Just TerminateServer <- fromException e
147+ = do
148+ logger logAction ERROR address Nothing
149+ $ withBraces
150+ $ T. pack (" Server terminated!" )
151+ throwTo serverThread (SomeAsyncException TerminateServer )
128152 | Just (ParseException msg) <- fromException e
129153 = logger logAction ERROR address Nothing
130154 $ withBraces
0 commit comments