Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC for redirecting stdio of child processes to open file handles #1055
Conversation
This comment has been minimized.
This comment has been minimized.
ipetkov
referenced this pull request
Apr 10, 2015
Closed
Implement two features: Creating OS pipes and redirecting stdio of child processes to File handles #24035
alexcrichton
reviewed
Apr 10, 2015
|
|
||
| ```rust | ||
| pub struct Pipe { | ||
| pub reader: File, |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 10, 2015
Member
Could you add some details on how this structure is going to be created as well? (the more detailed an RFC is the better!)
Also, I think that File may not be the best type to use here, or at least it may depend on the implementation. I would guess that on unix this would use the pipe system call, but File has methods like set_len which may not work out too well for pipes.
As a final point, could you add some words as to how this will be implemented on Windows as well?
This comment has been minimized.
This comment has been minimized.
ipetkov
Apr 12, 2015
Author
Contributor
@alexcrichton I think you are right that File isn't the correct type to use here. I was thinking as a File as an abstraction over a file descriptor/HANDLE, but I didn't really think about the distinctions between pipes and files. I'll update the design with a separate wrapper for pipes to maintain those distinctions!
alexcrichton
reviewed
Apr 10, 2015
| # Drawbacks | ||
|
|
||
| Implementing a design based on `File` borrows will require adding lifetime | ||
| parameters on stabilized `Stdio` and `Command` close to the 1.0 release. |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 10, 2015
Member
I think it's probably unlikely for a breaking change to add a lifetime parameter to Command to be accepted at this point, so this RFC may want to be worded with respect to keeping the same structure as-is today.
While not implemented today, I think that this could benefit from a "default lifetime" where we could define something like:
pub struct Command<'a = 'static> { ... }That way it would be backwards-compatible for us to add a lifetime parameter. Unfortunately though I don't believe it's implemented.
alexcrichton
reviewed
Apr 10, 2015
| ``` | ||
|
|
||
| Next, `std::process::Stdio` should provide a `redirect` method which accepts and | ||
| stores a `&File`, ensuring the underlying handle will not go out of scope |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 10, 2015
Member
On unix at least I know that you can use basically any file descriptor for stdout/stdin/stderr of a child process, but I'm less familiar with the situation on Windows. It may be worth exploring if the same set of primitives that can be used on Unix can be used on Windows, and perhaps they could all be accepted as part of this functions?
For example we may want at least (some day) an extension trait along the lines of:
pub trait StdioExt {
fn redirect_fd<T: AsRawFd>(t: &T) -> Self;
}That way you could do Stdio::redirect_fd(&tcp_stream) or Stdio::redirect_fd(&unix_socket) and it should all "just work". I don't think this wants to totally dive into the territory of "accept anything that's a file descriptor" just yet but it's at least worth considering.
This comment has been minimized.
This comment has been minimized.
ipetkov
Apr 12, 2015
Author
Contributor
Any potential problems you see with "accepting anything that's a file descriptor" at the time? If this is too broad what would you think about methods that only allow redirection with "blessed" types like a File or some sort of Pipe offered by the standard library?
This comment has been minimized.
This comment has been minimized.
rschmitt
commented
Apr 10, 2015
|
Isn't this what InheritFd did in old_io? Was that also implemented on Windows? |
alexcrichton
reviewed
Apr 10, 2015
| child process or redirect stdio to `/dev/null`. It would also be largely useful | ||
| to allow stdio redirection to any currently opened `std::fs::File` (henceforth | ||
| `File`) handle. This would allow redirecting stdio with a physical file or even | ||
| another process (via OS pipe) without forcing the parent process to buffer the |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 10, 2015
Member
When directing to another process, the pipe actually already exist in a sense where Stdio::piped() means that the child will be created with a stdin/stdout/stderr handle, under which is just a file descriptor. That may end up being more useful in the long run to hook up processes together perhaps?
This comment has been minimized.
This comment has been minimized.
ipetkov
Apr 12, 2015
Author
Contributor
It's true that the pipes exist internally, but they only pipe between the parent and child only. Although an API for hooking up several processes directly may be useful, I think that extending the current API with this functionality may be too troublesome to make it truly flexible, besides simply allowing the caller to pass in some sort of file descriptor and let them worry about hooking things up properly.
This comment has been minimized.
This comment has been minimized.
|
Overall I like the direction that this is going in, but I'm hesitant with how it interacts with other aspects of the system. For example this interface should largely boil down to "just passing a file descriptor" on unix and something like "just passing a HANDLE" on windows. Right now the restriction of using a Regardless though thanks for the RFC! |
This comment has been minimized.
This comment has been minimized.
|
@rschmitt Yeah InheritFd allowed redirection of arbitrary file descriptors, even on Windows, except the caller would have to manually convert the HANDLE as an fd via @alexcrichton I agree that using a
I think this would be a good approach for the future, but I wanted to avoid Unix/Windows discrepancies in the API around sockets (they aren't quite HANDLES on Windows and I'm not sure if they can be substituted for each other) so I thought about using a "white list" of accepted types for redirection. Although not as flexible as accepting the raw OS handle type it seems like a decent compromise for now. |
lilyball
reviewed
Apr 13, 2015
| ... | ||
| // sys::fs2::File is a safe wrapper over a fd/HANDLE, | ||
| // not to be confused with the public `std::fs::File` | ||
| Redirect(Rc<sys::fs2::File>), |
This comment has been minimized.
This comment has been minimized.
lilyball
Apr 13, 2015
Contributor
Using an Rc removes the Send and Sync bounds on Stdio, which is a breaking change.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Apr 14, 2015
| wrapped file handle. | ||
|
|
||
| ```rust | ||
| impl Clone for Stdio { ... } |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 14, 2015
Member
I feel that this may be a bit ambitious for the standard library, it's pretty rare to see an Arc under the hood of a primitive for the purpose of implementing Clone right now. There's definitely a use case of being able to share descriptors among spawned processes, but I think that we may want to expose that via raw handles for now.
For example unix could have Stdio::from_raw_fd and windows could have Stdio::from_raw_handle which will be low-level wrappers to pass I/O handles to the child. This would satisfy the ability to share the same handle among many processes (probably unsafely) for now as it's probably a somewhat rare desire.
This comment has been minimized.
This comment has been minimized.
ipetkov
Apr 15, 2015
Author
Contributor
I think the unsafety should be acceptable for now, especially since commands are probably executed as soon as they are built and all handles are still in scope.
Also I think it might be better to simply have a redirect method defined to take AsRawFd implementers on Unix, and AsRawHandle implementers on Windows to make usage of the API more portable. I'll update the proposal with the changes.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 15, 2015
Member
Also I think it might be better to simply have a redirect method defined to take AsRawFd implementers on Unix, and AsRawHandle implementers on Windows to make usage of the API more portable
You've mentioned this in the RFC as written, but this'll need to be a platform-specific extension as the set of types implementing AsRawFoo is different across platforms.
This comment has been minimized.
This comment has been minimized.
ipetkov
Apr 15, 2015
Author
Contributor
I was thinking something along the lines of a StdioExt trait which will be defined in std::os::$patform::io based on the appropriate AsRawFoo, but their method signatures would match lexically.
// Unix
pub trait StdioExt { fn redirect<T: AsRawFd>(t: &T) -> Stdio { ... } }
// Windows
pub trait StdioExt { fn redirect<T: AsRawHandle>(t: &T) -> Stdio { ... } }So then you could write something like the following in both Unix and Windows
#[cfg(unix)] use std::os::unix::io::StdioExt as StdioExt;
#[cfg(windows)] use std::os::windows::io::StdioExt as StdioExt;
let file = File::open(...);
let stdio = unsafe { StdioExt::redirect(file) };
...Instead of having to write the following everywhere you want to redirect something
#[cfg(unix)] use std::os::unix::io::StdioExt as StdioExt;
#[cfg(windows)] use std::os::windows::io::StdioExt as StdioExt;
let file = File::open(...);
let stdio = unsafe {
if cfg!(unix) {
StdioExt::from_raw_fd(file)
} else {
StdioExt::from_raw_handle(file)
}
};
...This way users are still somewhat aware that this is platform specific code, without suffering the constant need of cfg.
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Apr 14, 2015
| pub writer: PipeWriter, | ||
| } | ||
| pub struct PipeReader(sys::fs2::File); |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 14, 2015
Member
Out of curiosity, what's the use case you have in mind for a pipe? We should be able to implement AsRawFd and AsRawHandle for ChildStd{in,out,err} as well as being able to pass them into Stdio::redirect_foo, so I'm wondering if that would actually fit the bill for now.
I'm somewhat hesitant about adding three separate types (pair, reader, writer) as it'd be nice to keep the scope small for the RFC, but if it's needed it's needed! Could you be a little more precise here as well and indicate what functions are used to construct a Pipe and what module these are all located in?
This comment has been minimized.
This comment has been minimized.
ipetkov
Apr 15, 2015
Author
Contributor
Actually I completely failed to consider the possibility of extrancting the fd/HANDLE from ChildStd{in,out,err} (and my apologies for misunderstanding one of your earlier comments). It should work well for hooking up any number of processes together (in most cases) without having to create explicit pipes! (though it might still be nice to have a public interface for creating them)
I'll rework the proposal to include this strategy over the next few days!
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 15, 2015
Member
Yeah I definitely think we'll want to add a pipe-creation interface at some point, and it's well worth considering here, but it's always nice to not add if we don't need to!
alexcrichton
self-assigned this
Apr 15, 2015
This comment has been minimized.
This comment has been minimized.
|
I've updated and simplified the design to work with I've left out a public interface for safely creating OS pipes out of this version because it doesn't seem as necessary any more. It would be pretty trivial to expose a public wrapper over |
ipetkov
added some commits
Apr 18, 2015
alexcrichton
reviewed
Apr 20, 2015
| ``` | ||
|
|
||
| This would require that the internally defined `AnonPipe` wrapper be implemented | ||
| using HANDLEs (and not file descriptors) on Windows. This can easily be |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Apr 20, 2015
Member
This was actually recently fixed in rust-lang/rust#24426 as well, so yay!
This comment has been minimized.
This comment has been minimized.
|
This looks great to me @ipetkov, thanks! I'm a tad bit uncertain about whether cc @nagisa, @retep998, @carllerche, @aturon |
This comment has been minimized.
This comment has been minimized.
l0kod
commented
Apr 21, 2015
|
The
We should be able to connect the stdio/pipes before spawning any command. Agree with @alexcrichton to no use |
This comment has been minimized.
This comment has been minimized.
|
@l0kod the recommended unsafety here derives from the fact that there's no lifetime associated with the return value (see the section about backwards compatibility and my prior comment). It also already does take |
This comment has been minimized.
This comment has been minimized.
@l0kod That's one argument for exposing a public |
This comment has been minimized.
This comment has been minimized.
l0kod
commented
Apr 25, 2015
Well, I suggest to add a lifetime to the return value: rust-lang/rust#24251 (comment) |
This comment has been minimized.
This comment has been minimized.
|
@l0kod I think @alexcrichton is referring to |
This comment has been minimized.
This comment has been minimized.
l0kod
commented
Apr 25, 2015
|
I didn't realize this was so impacting. I know Rust has a big part stabilized but I understood that some (rare) breaking changes could happen before the final 1.0.0 |
This comment has been minimized.
This comment has been minimized.
What about redirecting multiple child processes to the same |
This comment has been minimized.
This comment has been minimized.
l0kod
commented
May 9, 2015
A |
This comment has been minimized.
This comment has been minimized.
|
I've updated the proposal to consider @l0kod Is |
aturon
added
the
T-libs
label
May 22, 2015
This comment has been minimized.
This comment has been minimized.
rschmitt
commented
Jun 5, 2015
|
In the meantime, is there a workaround we can use on nightly and/or stable? Heatseeker has only been usable on Windows ever since old_io was removed (relevant code). |
This comment has been minimized.
This comment has been minimized.
|
@ipetkov I cannot find |
This comment has been minimized.
This comment has been minimized.
|
@rschmitt there are two workarounds that come to mind:
|
This comment has been minimized.
This comment has been minimized.
rschmitt
commented
Jun 5, 2015
|
@ipetkov Your second suggestion is exactly what I needed. Works great for what I need, sure beats doing the ioctls by hand. Thanks! |
This comment has been minimized.
This comment has been minimized.
l0kod
commented
Jun 6, 2015
Even if tee(2) is not available, the |
This comment has been minimized.
This comment has been minimized.
|
@ipetkov could you update this RFC with specific signatures for the functions you have in mind? I've found it useful to have the signatures in mind ahead of time as it makes it easier to discuss portions of the API in terms of generics and ownership. |
This comment has been minimized.
This comment has been minimized.
|
@l0kod To my understanding of the Internal duplication of the OS handle is definitely a possibility, however, I personally lean towards allowing the caller to choose if they want duplication (and expecting them to use @alexcrichton Updated the RFC! |
alexcrichton
reviewed
Jun 15, 2015
| ensure the handle will remain valid until the child is spawned, making this | ||
| method `unsafe` as well. | ||
|
|
||
| Below are several alternative ways of exposing a high level API. They are |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 15, 2015
Member
I would recommend structuring this RFC to make a specific recommendation on which API route to choose and leave the other two to the "Alternatives" section. I would personally recommend something like:
- Having an extra
StdioExtstruct seems somewhat odd as it's not actually returned or needed to be a struct, so I'd list this as an alternative. - Using an extension trait is currently the standard method for extending functionality on a type, so I'd recommend listing this as the what the RFC proposes.
- I would recommend listing this as an alternative, but definitely emphasize the non-cross-platform aspect as it's the reason I wouldn't want to pursue it.
alexcrichton
reviewed
Jun 15, 2015
| // Unsafely borrow the handle, letting caller ensure it is valid | ||
| unsafe fn to_stdio<T: AsRaw*>(t: T) -> Stdio; | ||
| } | ||
| ``` |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 15, 2015
Member
Note that you could also do something like blending strategies 1/2:
pub trait StdioExt {
pub fn redirect<T: AsRawFd>(t: T) -> Stdio;
}
impl StdioExt for Stdio { ... }
alexcrichton
reviewed
Jun 15, 2015
|
|
||
| # Motivation | ||
|
|
||
| The current API in `std::process` allows to either pipe stdio between parent and |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 15, 2015
Member
I think that this motivation section may want to be updated with the current state of the standard library because with the FromRaw{Fd,Handle} implementations on Stdio it's actually possible to do this. It looks like this RFC gets us to a point of a slightly-more-ergonomic version of what we have today, but I think it would be worth it to explore the motivation to make sure the gains are worth the additions.
This comment has been minimized.
This comment has been minimized.
ipetkov
Jun 15, 2015
Author
Contributor
Ah, I hadn't realized those changes had landed! I'll update the RFC to just focus on a high-level design
ipetkov
added some commits
Jun 15, 2015
This comment has been minimized.
This comment has been minimized.
|
Updated the RFC to recognize the |
alexcrichton
reviewed
Jun 17, 2015
| Unfortunately, since the actual methods pertaining to `FromRaw*` and `AsRaw*` | ||
| are OS specific, their usage requires either constant `cfg` checks or OS | ||
| specific code duplication. Moreover, the conversion to `Stdio` is `unsafe` and | ||
| requires the caller to ensure the OS handle remains valid until spawning the |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 17, 2015
Member
The API is actually a little subtle in this regard, but the contract of FromRawFd is that it takes ownership of the file descriptor (or handle) in question, so the unsafety is based on ownership, not on the lifetime of the resource itself. Specifically, the various primitives in the standard library provide the guarantee that they are the sole owner of the fd/handle in question, and this being safe would otherwise be a violation of that.
Also, if we assume that a IntoRawFd trait exists, using the raw underlying interfaces may not be so bad as you'd just have two functions (unix/windows) doing Stdio::from_raw_fd(file.into_raw_fd()). I suspect that adding traits like IntoRawFd are somewhat inevitable.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 17, 2015
Member
To be more concrete, one concern I have about this is that this RFC is based on an ergonomic concern which will be alleviated soon in the future. The alternative (IntoRawFd) I don't think is unergonomic enough to warrant a specialized function.
This comment has been minimized.
This comment has been minimized.
ipetkov
Jun 18, 2015
Author
Contributor
the contract of FromRawFd is that it takes ownership of the file descriptor (or handle) in question
Ah, I had overlooked this...
However, doesn't this require that the caller mem::forget the wrapper over the descriptor/HANDLE or at least manually dup it (and perform error checking)? Because the contract of AsRawFd does not transfer ownership and then both Stdio and the original wrapper will both try to close the handle.
I suspect that adding traits like
IntoRawFdare somewhat inevitable.
I also agree, however, I feel like something like IntoRawFd could be very useful now, because being forced to mem::forget or dup handles (i.e. when using the current state of the API) is very unergonomic to force on userland, especially when the standard library does such a good job from abstracting much of that away (and the reason why the Command interface is so handy to have).
The alternative (
IntoRawFd) I don't think is unergonomic enough to warrant a specialized function.
I could definitely live with a compromise like IntoRawFd, but a specialized function could avoid accidental use of AsRawFd instead of IntoRawFd and prevent someone from shooting their foot (since both traits would produce a fd which FromRawFd will happily accept).
Assuming the availability of an IntoRawFd trait the currently proposed API could be tweaked to use it more appropriately:
pub trait StdioExt {
// Take ownership of the handle
fn redirect<T: IntoRaw*>(t: T) -> Stdio;
// Unsafely borrow the handle, letting caller ensure it is valid
unsafe fn redirect_by_ref<T: AsRaw*>(t: &T) -> Stdio;
}
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 18, 2015
Member
However, doesn't this require that the caller mem::forget the wrapper over the descriptor/HANDLE or at least manually dup it (and perform error checking)?
I believe this is correct, yes.
Assuming the availability of an IntoRawFd trait the currently proposed API could be tweaked to use it more appropriately:
I think one part of this that I'm uneasy about is how Stdio::redirect(t) is the same as Stdio::from_raw_fd(t.into_raw_fd()). Along those lines it seems redundant to have the redirect method, and then only adding an unsafe redirect_by_ref method also seems somewhat out of place because it's relatively the same thing as Stdio::from_raw_fd(f.as_raw_fd()) + a mem::forget somewhere.
This comment has been minimized.
This comment has been minimized.
ipetkov
Jun 19, 2015
Author
Contributor
You have a point there; I suppose that minus the cross-platform benefits the RFC's design does not offer any further benefits than something like IntoRawFd.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 19, 2015
Member
Ok, in that case it may be good motivation to push harder on these traits perhaps?
This comment has been minimized.
This comment has been minimized.
ipetkov
Jun 19, 2015
Author
Contributor
I believe so. Do you think this RFC would be an appropriate place to discuss them?
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 21, 2015
Member
I think this would serve as good motivation, but I think that IntoRaw{Fd,Handle,Socket} may want to be its own RFC.
This comment has been minimized.
This comment has been minimized.
|
Based on @alexcrichton's feedback I've written up RFC #1174 to discuss adding new traits such as This RFC still retains its cross-platform benefits over using |
alexcrichton
added
the
final-comment-period
label
Jul 1, 2015
This comment has been minimized.
This comment has been minimized.
|
This RFC is now entering its week long final comment period. |
This comment has been minimized.
This comment has been minimized.
#[cfg(unix)] use std::os::unix::process::StdioExt;
#[cfg(windows)] use std::os::windows::process::StdioExt;If some form of I’d go even further: rather than have |
This comment has been minimized.
This comment has been minimized.
|
@SimonSapin a given type may not implement both Taking an |
This comment has been minimized.
This comment has been minimized.
|
The consensus of the libs team on this RFC is to close this in favor of the |
alexcrichton
closed this
Jul 8, 2015
This comment has been minimized.
This comment has been minimized.
|
Thanks for all the feedback on this RFC anyway @alexcrichton! |
ipetkov commentedApr 10, 2015
Rendered