-
Notifications
You must be signed in to change notification settings - Fork 87
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
Conversation
-- --------------------------------------------------------------------------- | ||
-- executeAndWait | ||
|
||
-- | Create a new process and wait for it's termination. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"its"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really should stop making this typo...
-- --------------------------------------------------------------------------- | ||
-- executeAndWait | ||
|
||
-- | Create a new process and wait for it's termination. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please explain how this is different from createProcess
followed by waitForProcess
. It's quite worrying to have two APIs for the same thing that have subtly different behaviour, but I didn't follow all the details of the problem. Is there really no way to make waitForProcess
do the right thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so the basic problem is this:
Say you have two programs A
and B
. Now A
calls B
. There are two ways to do this.
- You can use the normal
CreateProcess
API, which is what normal Windows code do. Using this approach, the currentwaitForProcess
works absolutely fine. - You can call the emulated POSIX function
_exec
, which of course is supposed to allow the child process to replace the parent.
With approach 2)
waitForProcess
falls apart because the Win32's process model does not allow this the same way as linux. _exec
is emulated by first making a call to CreateProcess
to spawn B
and then immediately exiting from A
. So you have two different processes.
waitForProcess
is waiting on the termination of A
. Because A
is immediately killed, waitForProcess
will return even though B
is still running. This is why for instance the testsuite on Windows had lots of file locked errors.
This approach creates a new Job
and assigned A
to the job, but also all future processes spawned by A
. This allows us to listen in on events, such as, when all processes in the job are finished, but also allows us to propagate exit codes from _exec
calls.
The only reason we need this at all is because we don't interact with just actual native code on Windows, and instead have a lot of ported POSIX code.
I could add a new constructor to ProcessHandle
and make the decision on what to do internally in the functions. This would probably generate warnings in user code that deconstruct ProcessHandle
though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to confirm my understanding: with the current API on Linux, if we have:
- Process A calls
rawSystem "B" []
- Process B calls
createProcess $ proc "C" []
and then exits
Then the rawSystem
call in A
will return when B
exits, even if C
is still running. With this new API on Windows, A
would block until both B
and C
exit. Is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Mistuke that's a really good explanation, thanks. (it would be great to include this as a comment in the code)
I could add a new constructor to ProcessHandle
But how would you know when to do that? Would this entail creating the Job for every process we spawn?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@snoyberg By default, yes. However CreateProcess
has a flag CREATE_BREAKAWAY_FROM_JOB
that allows a child process to break away from any parent job. So you can get the same behaviour as on linux by B
using this flag when it spawns C
. Though I agree, it is subtle.. (There's also a flag to prevent any children from breaking away, so it goes both ways :))
@simonmar I'll update the comment.
So there are two alternate approaches that could be taken here:
- always use a job. In which case, if a user hasn't been deconstructing
ProcessHandle
then it should work transparently. - Combine the new function with the old one instead of having two calls now, and make the behaviour implicit on flag. But this would not be backwards compatible, since of course there's a new parameter.
I went with separate functions to avoid always using a job and breaking backwards compatibility.
If we go for 1) then, according to some very statistically unsound testing, the added overhead is about 0.0021 ms
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should note, there's also one useful thing jobs give you: it allows you to get and set detailed resource limits on processes. e.g. not use more than x
memory, don't run longer than y
ms, etc.
Which is why I wanted to expose the job handle back to the user as well. Since while not provided here, the changes you would need to set these should be rather trivial now, this is what they are in general used for along with just monitoring processes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- always use a job. In which case, if a user hasn't been deconstructing ProcessHandle then it should work transparently.
...
If we go for 1) then, according to some very statistically unsound testing, the added overhead is about 0.0021 ms.
Seems like a no-brainer to me. 2 microseconds per process creation, and only on Windows (where process creation is already many milliseconds, IIRC).
ProcessHandle
is already abstract in the basic System.Process
API, you have to import System.Process.Internals
to get it, so I can't imagine it would break much code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm so both of you seem to favor different options. How about a middle ground. I keep two separate functions for spawning, but only have one for waiting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, @simonmar and I do seem to be at odds. Simon: AFAICT, switching createProcess
to have the new job-based behavior is a significantly breaking semantic change on Windows only, which is a huge recipe for disaster. Do you disagree with this assessment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I see. What I'm concerned about is adding a new API that duplicates existing functionality but with subtly different behaviour on one platform. That's also a recipe for disaster.
What is using exec
on Windows?
Also, I found a bug with this version when doing a GHC bootstrapping build. I'll fix that soon. |
So, most blatant user at the moment are any msys utilities. Which we call
from ghc. I've patched for instance timeout, but the amount of usages
throughout I figured I should stop duplicating the code. Gcc also uses this
when calling the compiler from the driver. I believe the MINGW guys patch
these out to CreateProcess but not sure. Calling gcc for compilation seems
to work or we'd have problems already. Calling it for preprocessing seems
to subtly not work properly like in hsc2hs, again I can patch it in the
driver, but I'm duplicating code everywhere.
This also has a difference in the exit code. You'll get the exit code for
the last process to exit. So if using exec you get the right exit code now.
And when using CreateProcess since the last application would be the first
caller.
Is it really that confusing to have a 'createProcessWithJob' call?
…On Wed, 14 Dec 2016, 08:13 Simon Marlow, ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In System/Process.hs <#80>:
> @@ -870,6 +869,22 @@ It will therefore behave more portably between operating systems than 'system'.
The return codes and possible failures are the same as for 'system'.
-}
rawSystem :: String -> [String] -> IO ExitCode
-rawSystem cmd args = do
- (_,_,_,p) <- createProcess_ "rawSystem" (proc cmd args) { delegate_ctlc = True }
+rawSystem cmd args = executeAndWait "rawSystem" (proc cmd args) { delegate_ctlc = True }
+
+-- ---------------------------------------------------------------------------
+-- executeAndWait
+
+-- | Create a new process and wait for it's termination.
Yes, I see. What I'm concerned about is adding a new API that duplicates
existing functionality but with subtly different behaviour on one platform.
That's also a recipe for disaster.
What is using exec on Windows?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#80>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABH3Kc9RqPxvQG9bYf3wGSYSmOEaFxD7ks5rH6U5gaJpZM4LJ9UX>
.
|
What about the alternative of having a setting in
I think that will address the API proliferation issue (which I agree is a problem), and should hopefully be about as easy to work with from a user perspective. |
That works for me as well. So then we're back to one function. And I assume
we just leave the processes that do an execute and wait as they are now,
such as `rawSystem`.
…On Wed, 14 Dec 2016, 09:04 Michael Snoyman, ***@***.***> wrote:
What about the alternative of having a setting in CreateProcess that
switches to the job-based behavior, with the clear documentation that:
- It only affects Windows, and has no impact on other OSes
- It does not guarantee identical behavior to other OSes, but instead
will have slightly different exit code behavior as we've discussed here
I think that will address the API proliferation issue (which I agree is a
problem), and should hopefully be about as easy to work with from a user
perspective.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#80 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABH3KQ_fPXVqaKeDSGhogWfxBy2ohNtIks5rH7ESgaJpZM4LJ9UX>
.
|
That's my preference. There's been a move over the past few releases to make |
Ok,sounds good to me too. Thanks! |
BTW @Mistuke, I have not yet looked at the actual code on this at all, I figured we should hash out this design bit first. I'm assuming I should hold off on an actual code review until the PR is updated to reflect these decisions, do you agree? |
@SNoyman yeah, that would be best since the haskell side is going to
change anyway
…On Wed, 14 Dec 2016, 10:26 Michael Snoyman, ***@***.***> wrote:
BTW @Mistuke <https://github.com/Mistuke>, I have not yet looked at the
actual code on this at all, I figured we should hash out this design bit
first. I'm assuming I should hold off on an actual code review until the PR
is updated to reflect these decisions, do you agree?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#80 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABH3KZqZ78a7yuEffnTztiT8fzVIN05vks5rH8ROgaJpZM4LJ9UX>
.
|
f5027af
to
03d5d37
Compare
Sorry for the delay, I'm almost done, just have to figure out a weird issue with stage-2 when using this version to build gcc. |
f5027af
to
c4d9a7a
Compare
Fixed the issue, now ready for review. Thanks! |
Oops, forgot to add the note, I'll do that today. |
Ping |
I haven't had a chance to look into this yet, I'm hoping to this coming
week.
…On Fri, Jan 27, 2017 at 2:20 PM, Tamar Christina ***@***.***> wrote:
Ping
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#80 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AADBB4yNjBzmxTFurbuFL7DigT-yDj8Mks5rWeEQgaJpZM4LJ9UX>
.
|
Cool, no rush, just wasn't sure you knew it was updated :) |
Am aware, keep meaning to review, keep getting dragged away by less
interesting tasks :p
…On Sat, Jan 28, 2017 at 6:43 PM, Tamar Christina ***@***.***> wrote:
Cool, no rush, just wasn't sure you knew it was updated :)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#80 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AADBB2A43Ee5ZEHJphB6BmTFUyZ0wniDks5rW3AggaJpZM4LJ9UX>
.
|
@@ -604,7 +607,14 @@ waitForProcess ph@(ProcessHandle _ delegating_ctlc) = do | |||
when delegating_ctlc $ | |||
endDelegateControlC e | |||
return e | |||
|
|||
OpenExtHandle _ job iocp -> do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor note: looks like the do
is unnecessary
@@ -639,6 +650,10 @@ getProcessExitCode ph@(ProcessHandle _ delegating_ctlc) = do | |||
Just e | was_open && delegating_ctlc -> endDelegateControlC e | |||
_ -> return () | |||
return m_e | |||
where getHandle :: ProcessHandle__ -> PHANDLE | |||
getHandle (OpenHandle h) = h | |||
getHandle (ClosedHandle _) = error "getHandle: handle closed." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a hard requirement, but I'd prefer to avoid the partiality here. I was thinking something like:
case getHandle p_ of
Left e -> return (p_, (Just e, False))
Right h -> do ...
where
getHandle (OpenHandle h) = Right h
getHandle (ClosedHandle e) = Left e
getHandle (OpenExtHandle h _ _) = Right h
-- | ||
-- 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 system this flag is ignored. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/system/systems
-- | ||
-- Default: @False@ | ||
-- | ||
-- @since 1.x.x.x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should call this 1.5.0.0
-} | ||
data ProcessHandle__ = OpenHandle PHANDLE | ClosedHandle ExitCode | ||
data ProcessHandle__ = OpenHandle PHANDLE | ||
| OpenExtHandle PHANDLE PHANDLE PHANDLE |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
#ifndef WINDOWS | ||
pPrPr_disableITimers, c_execvpe, | ||
ignoreSignal, defaultSignal, | ||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For clarity: could you switch the conditional for #ifdef WINDOWS
? It's slightly easier to read that way.
@@ -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 set in CreateProcess since 1.4.?.? in order to create |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably "can be set", and again let's use 1.5.0.0.
@@ -1,4 +1,5 @@ | |||
{-# LANGUAGE CPP #-} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to review this file too, but my review will be much less authoritative. Basically: I'm assuming you know what's going on for Windows better than I do, which is a really safe assumption 😄.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol, fair enough :)
@@ -9,6 +9,8 @@ | |||
|
|||
* New exposed `withCreateProcess` | |||
* Derive `Show` and `Eq` for `CreateProcess`, `CmdSpec`, and `StdStream` | |||
* Add support for monitoring process tree for termination with the parameter `use_process_jobs` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go into "Unreleased changes"
@@ -1,5 +1,5 @@ | |||
name: process | |||
version: 1.4.3.0 | |||
version: 1.4.3.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, this would be 1.5.0.0
129dd48
to
f8b53d8
Compare
Seems the appveyor build is having trouble with sourceforge. |
I've restarted the AppVeyor build, which now passed. |
Thanks! What are your thoughts on roll-out of a new release for this? Do you want to get more testing of this with GHC before we release? |
It should be OK, the default behavior should be unchanged. Though I would
like to run a x86 build just to be sure. I'll run the full testsuite on
both tonight and report back. Other than that I'm fairly confident. A
similar code has been in use in timeout.hs for a while now. Thanks!
…On Mon, 30 Jan 2017, 12:52 Michael Snoyman, ***@***.***> wrote:
Thanks! What are your thoughts on roll-out of a new release for this? Do
you want to get more testing of this with GHC before we release?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#80 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABH3KTGw8qbaIkJyZ6818RKZV4oqLQ0Vks5rXd0JgaJpZM4LJ9UX>
.
|
Sure, thank you!
On Mon, Jan 30, 2017 at 3:05 PM, Tamar Christina <notifications@github.com>
wrote:
… It should be OK, the default behavior should be unchanged. Though I would
like to run a x86 build just to be sure. I'll run the full testsuite on
both tonight and report back. Other than that I'm fairly confident. A
similar code has been in use in timeout.hs for a while now. Thanks!
On Mon, 30 Jan 2017, 12:52 Michael Snoyman, ***@***.***>
wrote:
> Thanks! What are your thoughts on roll-out of a new release for this? Do
> you want to get more testing of this with GHC before we release?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <#80 (comment)>, or
mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/
ABH3KTGw8qbaIkJyZ6818RKZV4oqLQ0Vks5rXd0JgaJpZM4LJ9UX>
> .
>
—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
<#80 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AADBB0WYQN0qP3XHHR0GGLqOXvqPsVGNks5rXeACgaJpZM4LJ9UX>
.
|
Ok, I think we're good. Whenever you want to make the release is fine with me! After you do I can take care of updating the rest of the dependencies to get it in the GHC tree if it's easier for you! |
I've made the release to Hackage. If you could take care of the updates on the GHC side that would be great, much appreciated! |
Alright, I'll do that then. Thanks!
…On Mon, 6 Feb 2017, 08:58 Michael Snoyman, ***@***.***> wrote:
I've made the release to Hackage. If you could take care of the updates on
the GHC side that would be great, much appreciated!
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#80 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABH3KW3MGwfxQCxKcQI7CzVCxuY_at_oks5rZuCzgaJpZM4LJ9UX>
.
|
See #77
In the end I had to make a new API, since the handle returned by the Job should never be waited on.
Existing code would probably call the old wait function on it and then block indefinitely (or until a timeout if any was specified).
Instead I have created a new API and replaced the internals functions that do a fork/exec/wait with the new api.
I have also fixed the issue with the exit code from processes forked with
exec
not correctly being returned.