Skip to content
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

Clarify behaviour of close_fds on Windows #235

Open
robx opened this issue Feb 21, 2022 · 3 comments
Open

Clarify behaviour of close_fds on Windows #235

robx opened this issue Feb 21, 2022 · 3 comments

Comments

@robx
Copy link
Contributor

robx commented Feb 21, 2022

The documentation of close_fds states that:

Close all file descriptors except stdin, stdout and stderr in the new process (on Windows, only works if std_in, std_out, and std_err are all Inherit).

It's quite unclear what "only works" means. Will things blow up if I set close_fds and CreatePipe? My best guess is something like "if any of std_in, std_out, std_err are not Inherit, those will be closed too".

Specifically, based on the documentation, I can't answer the question whether

(rstdin, wstdin) <- createPipe
(_,_,_,p) <- createProcess (proc "cat" []) { std_in = UseHandle rstdin, close_fds = True }
hPutStr wstdin "foo"
hClose wstdin
waitProcess p

should work on Windows. (This hangs on Linux without close_fds, (apparently) because the child inherits the open write-end, causing the pipe to stay open despite hClose.)

@snoyberg
Copy link
Collaborator

@Mistuke do you have any thoughts here?

@Mistuke
Copy link
Contributor

Mistuke commented Feb 22, 2022

It's quite unclear what "only works" means. Will things blow up if I set close_fds and CreatePipe? My best guess is something like "if any of std_in, std_out, std_err are not Inherit, those will be closed too".

The answer to this is.. it depends.

So one clarification close_fds doesn't actually close anything on Windows. It only changes the state of the child process's ability to inherit any handles at all or not.

specifically, when close_fds is specified and stdin, stderr, stdout have not been redirected then we turns off handle inheritance completely. This behavior is an attempt to work around https://gitlab.haskell.org/ghc/ghc/-/issues/3231

should work on Windows. (This hangs on Linux without close_fds, (apparently) because the child inherits the open write-end, causing the pipe to stay open despite hClose.)

This is again, it depends. Point of clarification, FDs on Windows can't be inherited at all. FDs are a somewhat pseudo concept, in that the FD numbers are mapped inside each process. i.e. FD 3 in parent doesn't need to point to the same object as a child. The mapping is maintained in process and is unique for each process.

What can be inherited on Windows are HANDLE. Inside each process there's an FD <-> HANDLE table mapping if something like _open_osfhandle is called.

Now GHC on Windows has two I/O managers, and the behavior depends on the I/O manager:

So for:

(rstdin, wstdin) <- createPipe
(_,_,_,p) <- createProcess (proc "cat" []) { std_in = UseHandle rstdin, close_fds = True }
hPutStr wstdin "foo"
hClose wstdin
waitProcess p
  • On MIO

No, this wouldn't hang. createPipe creates uninheritable handles by default. During createProcess for any handles explicitly redirected we create a duplicate handle with the inherit state set to true. So the child never gets to see wstdin nor rstdin (directly).

  • On WinIO

Yes, this would hang because createPipe creates inheritable handles for both rstdin and wstdin and you have redirected a handle so we don't change the process handle inheritance state.

However most WinIO relies on being able to share all HANDLEs created.

The situation is not ideal... I think it would be better if createPipe took an argument to explicitly control inheritence state of HANDLEs or have a variant that explicitly creates non-inheritable Handles.

@robx
Copy link
Contributor Author

robx commented Feb 25, 2022

Thank you for the detailed explanation. I see how that is hard to document clearly :/

I like the general idea of providing an API where createPipe generally has "non-inheritable" handles -- my understanding that could also work be done on (some?) unix systems by setting CLOEXEC.

(I've solved my particular problem that prompted this issue by reverting to use CreatePipe instead of UseHandle -- don't actually know in how far the code works right on Windows, but I'm no longer potentially breaking things.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants