-
Notifications
You must be signed in to change notification settings - Fork 0
/
Child.hs
116 lines (105 loc) · 3.79 KB
/
Child.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
-- c-repl: a C read-eval-print loop.
-- Copyright (C) 2008 Evan Martin <[email protected]>
module Child where
import Prelude hiding (catch)
import Control.Concurrent
import Control.OldException
import Control.Monad.Error
import Data.Maybe
import System.Directory
import System.Exit
import System.Process
import System.IO
import System.Posix.IO (createPipe, fdToHandle)
import qualified Paths_c_repl
-- TODO: rewrite this to not use runProcess, as we want the real pid of
-- the child process (for attaching to it with gdb), and System.Process
-- only exposes ProcessHandles and no pids.
data Child = Child {
childPHandle :: ProcessHandle,
childPid :: Int, -- The actual process ID of this process.
childCommand :: Handle,
childResponse :: Handle
}
findChildBinary :: IO (Either String FilePath)
findChildBinary = do
let path = "dist/build/c-repl-child"
ok1 <- isReadable path
if ok1
then return (Right path)
else do
libexecdir <- Paths_c_repl.getLibexecDir
let path = libexecdir ++ "/c-repl-child"
ok2 <- isReadable path
if ok2
then return (Right path)
else return (throwError "can't find child executable")
where
isReadable path =
do
perms <- getPermissions path
return $ readable perms
`catch` \e -> return False
start :: IO (Either String Child)
start = do
(commandR, commandW) <- createPipe
(responseR, responseW) <- createPipe
childPath <- findChildBinary
case childPath of
Left err -> return (Left err)
Right childPath -> do
phandle <- runProcess childPath
[show commandR, show responseW]
Nothing{-working dir-} Nothing{-env-}
Nothing Nothing Nothing {-stdin,out,err-}
[commandH, responseH] <- mapM fdToHandle [commandW, responseR]
mapM_ (\h -> hSetBuffering h LineBuffering) [commandH, responseH]
pidstr <- hGetLine responseH
return $ Right $ Child phandle (read pidstr) commandH responseH
stop :: Child -> IO ()
stop child = terminateProcess (childPHandle child)
run :: Child -> Int -> IO (Either String ())
run child entry = runErrorT (sendCommand >> awaitResponse) where
command = show entry
sendCommand = liftIO $ hPutStrLn (childCommand child) command
awaitResponse :: ErrorT String IO ()
awaitResponse = do
-- Set up a thread that fills in a MVar if the child responds.
respMVar <- liftIO $ do
respMVar <- newEmptyMVar
forkIO $ do
resp <- hGetLine (childResponse child)
putMVar respMVar resp
return respMVar
-- Wait up to 5s for a response.
resp <- checkResponse respMVar 5000
-- Check that the response is as we expect.
if resp == command
then return ()
else throwError "got bad response from child"
checkResponse :: MVar String -> Int -> ErrorT String IO String
checkResponse respMVar ms = do
resp <- liftIO $ tryTakeMVar respMVar
case resp of
Just resp -> return resp
Nothing -> do -- still working?
-- The subprocess hasn't responded yet. Check if it died.
-- (Sometimes getProcessExitCode throws an interrupted exception;
-- we interpret that as a crash as well.)
dead <- liftIO $ isDead child
if dead
then throwError "(child exited)"
else if ms <= 0
then do
-- We've waited too long. (XXX prompt the user here)
liftIO $ terminateProcess (childPHandle child)
throwError "(child hung?)"
else do
-- Wait a bit longer for a response.
liftIO $ threadDelay 100
checkResponse respMVar (ms-100)
isDead :: Child -> IO Bool
isDead child = catchJust ioErrors getExited (\e -> return True) where
getExited = do
exit <- getProcessExitCode (childPHandle child)
return $ isJust exit