15
15
{-# LANGUAGE NamedFieldPuns #-}
16
16
{-# LANGUAGE DeriveGeneric #-}
17
17
{-# LANGUAGE LambdaCase #-}
18
+ {-# LANGUAGE ViewPatterns #-}
18
19
----------------------------------------------------------------------------
19
20
module DAP.Server
20
21
( runDAPServer
21
22
, runDAPServerWithLogger
22
23
, readPayload
24
+ , TerminateServer (.. )
23
25
) where
24
26
----------------------------------------------------------------------------
25
27
import Control.Monad ( when , forever )
28
+ import Control.Concurrent ( ThreadId , myThreadId , throwTo )
26
29
import Control.Concurrent.MVar ( newMVar )
27
30
import Control.Concurrent.STM ( newTVarIO )
28
- import Control.Exception ( SomeException
31
+ import Control.Exception ( Exception
32
+ , SomeAsyncException (.. )
33
+ , SomeException
29
34
, IOException
30
35
, catch
31
36
, fromException
37
+ , toException
32
38
, throwIO )
33
39
import Control.Monad ( void )
34
40
import Data.Aeson ( decodeStrict , eitherDecode , Value , FromJSON )
@@ -42,6 +48,7 @@ import System.IO ( hClose, hSetNewlineMode, Handle, N
42
48
, NewlineMode (NewlineMode , outputNL , inputNL )
43
49
, IOMode (ReadWriteMode ), stderr , hPrint )
44
50
import System.IO.Error ( isEOFError )
51
+ import System.Exit ( exitWith , ExitCode (ExitSuccess ) )
45
52
import Text.Read ( readMaybe )
46
53
import qualified Data.ByteString.Lazy.Char8 as BL8
47
54
import qualified Data.ByteString.Char8 as BS
@@ -63,7 +70,11 @@ stdoutLogger = do
63
70
withLock handleLock $ do
64
71
T. putStrLn msg
65
72
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 )
67
78
68
79
runDAPServer :: ServerConfig -> (Command -> Adaptor app Request () ) -> IO ()
69
80
runDAPServer config communicate = do
@@ -81,13 +92,19 @@ runDAPServerWithLogger rawLogAction serverConfig@ServerConfig {..} communicate =
81
92
let logAction = cfilter (\ msg -> if debugLogging then True else severity msg /= DEBUG ) rawLogAction
82
93
logAction <& (mkDebugMessage $ (T. pack (" Running DAP server on " <> show port <> " ..." )))
83
94
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
91
108
92
109
-- | Initializes the Adaptor
93
110
--
@@ -120,11 +137,18 @@ serviceClient communicate lcl = forever $ runAdaptorWith lcl st $ do
120
137
where
121
138
st = AdaptorState MessageTypeResponse []
122
139
----------------------------------------------------------------------------
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
126
144
let
127
145
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 )
128
152
| Just (ParseException msg) <- fromException e
129
153
= logger logAction ERROR address Nothing
130
154
$ withBraces
0 commit comments