Skip to content

Add new API that can correctly wait for termination of processes forked with exec on Windows. #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
684ce18
GH77: Add scaffolding.
Mistuke Dec 3, 2016
8080309
GH77: Fixed compilation
Mistuke Dec 4, 2016
d71248a
GH77: Add terminate job
Mistuke Dec 4, 2016
57e0c7f
GH77: Update readme and export list.
Mistuke Dec 4, 2016
f6de652
GH77: Replaced system and rawSystem
Mistuke Dec 4, 2016
3f440e2
GH77: Updated readme
Mistuke Dec 4, 2016
86b273c
GH77: Fix tests
Mistuke Dec 4, 2016
e7827bb
GH77: Add failing test for Windows.
Mistuke Dec 4, 2016
3bf217f
GH77: Working
Mistuke Dec 10, 2016
282aa2e
GH77: Finish implementation.
Mistuke Dec 10, 2016
e89d6e1
GH77: Update testsuite.
Mistuke Dec 10, 2016
2e3542d
GH77: update tests.
Mistuke Dec 11, 2016
eb85aac
GH77: fix tests
Mistuke Dec 11, 2016
605ce3e
GH77: Accept output.
Mistuke Dec 11, 2016
3a5935c
GH77: rewrote implementation.
Mistuke Jan 2, 2017
ae57e8c
GH77: fix compile errors.
Mistuke Jan 2, 2017
7ef688e
GH77: Update readme.
Mistuke Jan 5, 2017
c3c067b
GH77: restored compatibility.
Mistuke Jan 7, 2017
5a12fa4
GH77: rebased.
Mistuke Jan 7, 2017
e41616e
GH77: fix Posix.
Mistuke Jan 8, 2017
5a0d7bc
GH77: remove typo.
Mistuke Jan 8, 2017
ad967f8
GH77: fix pattern matching posix.
Mistuke Jan 8, 2017
2d6933b
GH77: replace <$> with fmap
Mistuke Jan 8, 2017
4a423ad
GH77: Add appropriate ifdefs.
Mistuke Jan 8, 2017
94a2140
GH77: fixed bug.
Mistuke Jan 16, 2017
523b3dd
GH77: Added note.'
Mistuke Jan 17, 2017
0f7b948
Updated based on review
Mistuke Jan 29, 2017
f8b53d8
rebased and set back WINDOWS_CCONV
Mistuke Jan 29, 2017
9bcbaeb
fix build.
Mistuke Jan 29, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions System/Process.hs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ proc cmd args = CreateProcess { cmdspec = RawCommand cmd args,
create_new_console = False,
new_session = False,
child_group = Nothing,
child_user = Nothing }
child_user = Nothing,
use_process_jobs = False }

-- | Construct a 'CreateProcess' record for passing to 'createProcess',
-- representing a command to be passed to the shell.
Expand All @@ -133,7 +134,8 @@ shell str = CreateProcess { cmdspec = ShellCommand str,
create_new_console = False,
new_session = False,
child_group = Nothing,
child_user = Nothing }
child_user = Nothing,
use_process_jobs = False }

{- |
This is the most general way to spawn an external process. The
Expand Down Expand Up @@ -594,8 +596,9 @@ waitForProcess ph@(ProcessHandle _ delegating_ctlc) = do
throwErrnoIfMinus1Retry_ "waitForProcess" (c_waitForProcess h pret)
modifyProcessHandle ph $ \p_' ->
case p_' of
ClosedHandle e -> return (p_',e)
OpenHandle ph' -> do
ClosedHandle e -> return (p_', e)
OpenExtHandle{} -> return (p_', ExitFailure (-1))
OpenHandle ph' -> do
closePHANDLE ph'
code <- peek pret
let e = if (code == 0)
Expand All @@ -605,7 +608,14 @@ waitForProcess ph@(ProcessHandle _ delegating_ctlc) = do
when delegating_ctlc $
endDelegateControlC e
return e

OpenExtHandle _ job iocp ->
#if defined(WINDOWS)
maybe (ExitFailure (-1)) mkExitCode `fmap` waitForJobCompletion job iocp timeout_Infinite
where mkExitCode code | code == 0 = ExitSuccess
| otherwise = ExitFailure $ fromIntegral code
#else
return $ ExitFailure (-1)
#endif

-- ----------------------------------------------------------------------------
-- getProcessExitCode
Expand All @@ -624,22 +634,29 @@ getProcessExitCode ph@(ProcessHandle _ delegating_ctlc) = do
(m_e, was_open) <- modifyProcessHandle ph $ \p_ ->
case p_ of
ClosedHandle e -> return (p_, (Just e, False))
OpenHandle h ->
open -> do
alloca $ \pExitCode -> do
res <- throwErrnoIfMinus1Retry "getProcessExitCode" $
c_getProcessExitCode h pExitCode
code <- peek pExitCode
if res == 0
then return (p_, (Nothing, False))
else do
closePHANDLE h
let e | code == 0 = ExitSuccess
| otherwise = ExitFailure (fromIntegral code)
return (ClosedHandle e, (Just e, True))
case getHandle open of
Nothing -> return (p_, (Nothing, False))
Just h -> do
res <- throwErrnoIfMinus1Retry "getProcessExitCode" $
c_getProcessExitCode h pExitCode
code <- peek pExitCode
if res == 0
then return (p_, (Nothing, False))
else do
closePHANDLE h
let e | code == 0 = ExitSuccess
| otherwise = ExitFailure (fromIntegral code)
return (ClosedHandle e, (Just e, True))
case m_e of
Just e | was_open && delegating_ctlc -> endDelegateControlC e
_ -> return ()
return m_e
where getHandle :: ProcessHandle__ -> Maybe PHANDLE
getHandle (OpenHandle h) = Just h
getHandle (ClosedHandle _) = Nothing
getHandle (OpenExtHandle h _ _) = Just h


-- ----------------------------------------------------------------------------
Expand All @@ -664,8 +681,13 @@ terminateProcess :: ProcessHandle -> IO ()
terminateProcess ph = do
withProcessHandle ph $ \p_ ->
case p_ of
ClosedHandle _ -> return ()
OpenHandle h -> do
ClosedHandle _ -> return ()
#if defined(WINDOWS)
OpenExtHandle{} -> terminateJob ph 1 >> return ()
#else
OpenExtHandle{} -> error "terminateProcess with OpenExtHandle should not happen on POSIX."
#endif
OpenHandle h -> do
throwErrnoIfMinus1Retry_ "terminateProcess" $ c_terminateProcess h
return ()
-- does not close the handle, we might want to try terminating it
Expand Down
25 changes: 23 additions & 2 deletions System/Process/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module System.Process.Common
, StdStream (..)
, ProcessHandle(..)
, ProcessHandle__(..)
, ProcRetHandles (..)
, withFilePathException
, PHANDLE
, modifyProcessHandle
Expand Down Expand Up @@ -94,13 +95,27 @@ data CreateProcess = CreateProcess{
-- Default: @Nothing@
--
-- @since 1.4.0.0
child_user :: Maybe UserID -- ^ Use posix setuid to set child process's user id; does nothing on other platforms.
child_user :: Maybe UserID, -- ^ Use posix setuid to set child process's user id; does nothing on other platforms.
--
-- Default: @Nothing@
--
-- @since 1.4.0.0
use_process_jobs :: Bool -- ^ On Windows systems this flag indicates that we should wait for the entire process tree
-- to finish before unblocking. On POSIX systems this flag is ignored.
--
-- Default: @False@
--
-- @since 1.5.0.0
} deriving (Show, Eq)

-- | contains the handles returned by a call to createProcess_Internal
data ProcRetHandles
= ProcRetHandles { hStdInput :: Maybe Handle
, hStdOutput :: Maybe Handle
, hStdError :: Maybe Handle
, procHandle :: ProcessHandle
}

data CmdSpec
= ShellCommand String
-- ^ A command line to execute using the shell
Expand Down Expand Up @@ -154,8 +169,14 @@ data StdStream
None of the process-creation functions in this library wait for
termination: they all return a 'ProcessHandle' which may be used
to wait for the process later.

On Windows a second wait method can be used to block for event
completion. This requires two handles. A process job handle and
a events handle to monitor.
-}
data ProcessHandle__ = OpenHandle PHANDLE | ClosedHandle ExitCode
data ProcessHandle__ = OpenHandle PHANDLE
| OpenExtHandle PHANDLE PHANDLE PHANDLE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to make these fields strict? It does not seem like laziness would ever be a virtue here. (I realize that will make it mismatched with the other constructors, still asking if it's the right thing to do.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm don't think so in this case, since the only way they're used is with a foreign value. I don't think you can ever get a lazy value from an FFI call right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My question is really: would we ever want the laziness here? I would generally make fields strict if there's no reason to do otherwise, since it excludes a potential source of bugs and confusion. Also, in this case, I think it can (slightly) help performance by allowing unpacking of a primitive type, but that's a minor concern. Either way, this isn't vital.

| ClosedHandle ExitCode
data ProcessHandle = ProcessHandle !(MVar ProcessHandle__) !Bool

withFilePathException :: FilePath -> IO a -> IO a
Expand Down
28 changes: 24 additions & 4 deletions System/Process/Internals.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ module System.Process.Internals (
PHANDLE, closePHANDLE, mkProcessHandle,
modifyProcessHandle, withProcessHandle,
CreateProcess(..),
CmdSpec(..), StdStream(..),
CmdSpec(..), StdStream(..), ProcRetHandles (..),
createProcess_,
runGenProcess_, --deprecated
fdToHandle,
startDelegateControlC,
endDelegateControlC,
stopDelegateControlC,
#ifndef WINDOWS
unwrapHandles,
#ifdef WINDOWS
terminateJob,
waitForJobCompletion,
timeout_Infinite,
#else
pPrPr_disableITimers, c_execvpe,
ignoreSignal, defaultSignal,
#endif
Expand All @@ -57,7 +62,6 @@ import System.Process.Posix
#endif

-- ----------------------------------------------------------------------------

-- | This function is almost identical to
-- 'System.Process.createProcess'. The only differences are:
--
Expand All @@ -66,6 +70,18 @@ import System.Process.Posix
-- * This function takes an extra @String@ argument to be used in creating
-- error messages.
--
-- * 'use_process_jobs' can be set in CreateProcess since 1.5.0.0 in order to create
-- an I/O completion port to monitor a process tree's progress on Windows.
--
-- The function also returns two new handles:
-- * an I/O Completion Port handle on which events
-- will be signaled.
-- * a Job handle which can be used to kill all running
-- processes.
--
-- On POSIX platforms these two new handles will always be Nothing
--
--
-- This function has been available from the "System.Process.Internals" module
-- for some time, and is part of the "System.Process" module since version
-- 1.2.1.0.
Expand All @@ -75,7 +91,7 @@ createProcess_
:: String -- ^ function name (for error messages)
-> CreateProcess
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
createProcess_ = createProcess_Internal
createProcess_ msg proc_ = unwrapHandles `fmap` createProcess_Internal msg proc_
{-# INLINE createProcess_ #-}

-- ------------------------------------------------------------------------
Expand Down Expand Up @@ -137,6 +153,10 @@ translate :: String -> String
translate = translateInternal
{-# INLINE translate #-}

-- ---------------------------------------------------------------------------
-- unwrapHandles
unwrapHandles :: ProcRetHandles -> (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
unwrapHandles r = (hStdInput r, hStdOutput r, hStdError r, procHandle r)

-- ----------------------------------------------------------------------------
-- Deprecated / compat
Expand Down
13 changes: 9 additions & 4 deletions System/Process/Posix.hs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ withCEnvironment envir act =
createProcess_Internal
:: String
-> CreateProcess
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO ProcRetHandles
createProcess_Internal fun
CreateProcess{ cmdspec = cmdsp,
cwd = mb_cwd,
Expand Down Expand Up @@ -166,7 +166,11 @@ createProcess_Internal fun
hndStdError <- mbPipe mb_stderr pfdStdError ReadMode

ph <- mkProcessHandle proc_handle mb_delegate_ctlc
return (hndStdInput, hndStdOutput, hndStdError, ph)
return ProcRetHandles { hStdInput = hndStdInput
, hStdOutput = hndStdOutput
, hStdError = hndStdError
, procHandle = ph
}

{-# NOINLINE runInteractiveProcess_lock #-}
runInteractiveProcess_lock :: MVar ()
Expand Down Expand Up @@ -291,7 +295,8 @@ interruptProcessGroupOfInternal
interruptProcessGroupOfInternal ph = do
withProcessHandle ph $ \p_ -> do
case p_ of
ClosedHandle _ -> return ()
OpenHandle h -> do
OpenExtHandle{} -> return ()
ClosedHandle _ -> return ()
OpenHandle h -> do
pgid <- getProcessGroupIDOf h
signalProcessGroup sigINT pgid
Loading