-
Notifications
You must be signed in to change notification settings - Fork 24
/
Child.hs
132 lines (118 loc) · 4.17 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
-- c-repl: a C read-eval-print loop.
-- Copyright (C) 2008 Evan Martin <[email protected]>
-- This module is responsible for managing the child process that actually
-- executes the code snippets.
module Child (
Child(..),
-- Start/stop a child subprocess.
start,
stop,
-- Instruct a child subprocess to run some code.
run
) where
import Prelude hiding (catch)
import Control.Concurrent
import Control.Exception
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
}
-- Compute the location of the child helper.
findChildBinary :: IO (Maybe FilePath)
findChildBinary = do
let path = "dist/build/c-repl-child"
ok1 <- isReadable path
if ok1
then return (Just path)
else do
libexecdir <- Paths_c_repl.getLibexecDir
let path = libexecdir ++ "/c-repl-child"
ok2 <- isReadable path
if ok2
then return (Just path)
else return Nothing
where
isReadable path =
do
perms <- getPermissions path
return $ readable perms
`catch` \e -> return False
-- Create a new Child, starting the helper process.
start :: IO (Either String Child)
start = do
(commandR, commandW) <- createPipe
(responseR, responseW) <- createPipe
childPath <- findChildBinary
case childPath of
Nothing -> return $ throwError "couldn't find helper binary"
Just 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
-- Kill off a Child.
stop :: Child -> IO ()
stop child = terminateProcess (childPHandle child)
-- Command a Child to run modules up to a given id.
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