-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC for redirecting stdio of child processes to open file handles #1055
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
Changes from 1 commit
dc55144
c854d08
2d67ee4
4e19aef
2f0e002
8b8a468
4ac365e
70e4043
113bd5a
690d99b
a8e1c8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| - Feature Name: process-stdio-redirection | ||
| - Start Date: 2015-04-10 | ||
| - RFC PR: | ||
| - Rust Issue: | ||
|
|
||
| # Summary | ||
|
|
||
| Update the `std::process` API with the ability to redirect stdio of child | ||
| processes to any opened file handle. | ||
|
|
||
| # Motivation | ||
|
|
||
| The current API in `std::process` allows to either pipe stdio between parent and | ||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When directing to another process, the pipe actually already exist in a sense where
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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. |
||
| data itself. | ||
|
|
||
| For example, one may wish to spawn a process which prints gigabytes | ||
| of data (e.g. logs) and use another process to filter through it, and save the | ||
| result to a file. The current API would force the parent process to dedicate a | ||
| thread just to buffer all data between child processes and disk, which is | ||
| impractical given that the OS can stream the data for us via pipes and file | ||
| redirection. | ||
|
|
||
| # Detailed design | ||
|
|
||
| First, the standard library should provide an OS agnostic way of creating OS | ||
| in-memory pipes, i.e. a `Pipe`, providing reader and writer handles as `File`s. | ||
| This would avoid the need for users to write OS specific (and `unsafe`) code | ||
| while retaining all the benefits offered by `File`. This proposal considers | ||
| making the `Pipe`'s reader and writer fields public to allow easily moving | ||
| ownership of the two handles, but any other appropriate interface is acceptable. | ||
|
|
||
| ```rust | ||
| pub struct Pipe { | ||
| pub reader: File, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 As a final point, could you add some words as to how this will be implemented on Windows as well?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexcrichton I think you are right that |
||
| pub writer: File, | ||
| } | ||
| ``` | ||
|
|
||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||
| unexpectedly before the child is spawned. The spawning implementation can then | ||
| extract and use the `File`'s OS specific handle when creating the child | ||
| process. | ||
|
|
||
| This `File` reference should be an immutable one, to allow "reuse" of the handle | ||
| across several `Command`s simultaneously. This, however, can allow code to | ||
| indirectly mutate a `File` through an immutable reference by passing it on to a | ||
| child process, although retrieving and mutating through the underlying OS handle | ||
| (via `AsRaw{Fd, Socket, Handle}`) is already possible through a `&File`. Thus | ||
| this API would not introduce any "mutability leaks" of `File`s that were not | ||
| already present. | ||
|
|
||
| This design also offers benefits when the user may wish to close (drop) a | ||
| `File`, for example, closing the read end of a pipe and sending EOF to its | ||
| child. The compiler can infer the lifetimes of all references to the original | ||
| `File`, eliminating some guesswork on whether all ends of a pipe have been | ||
| closed. This would not be possible in a design which duplicates the OS handle | ||
| internally which could more easily lead to a deadlock (e.g. waiting on a child | ||
| to exit while the same scope holds an open pipe to the child's stdin). | ||
|
|
||
| Reclaiming ownership of the borrowed `File` may require locally scoping the | ||
| creation of `Stdio` or `Command` instances to force their lifetimes to end, | ||
| however, this is minimally intrusive compared to alternative designs. | ||
|
|
||
| # Drawbacks | ||
|
|
||
| Implementing a design based on `File` borrows will require adding lifetime | ||
| parameters on stabilized `Stdio` and `Command` close to the 1.0 release. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's probably unlikely for a breaking change to add a lifetime parameter to 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. |
||
|
|
||
| # Alternatives | ||
|
|
||
| Do nothing now and choose a stability compatible design, possibly being stuck | ||
| with less ergonomic APIs. | ||
|
|
||
| One alternative strategy is to duplicate the underlying OS handle and have the | ||
| `std::process` APIs take ownership of the copy. When working with OS pipes, | ||
| however, the user would have to manually keep track where the duplicates have | ||
| gone if they wish to close them all; failing to do so may cause deadlocks. | ||
|
|
||
| Another strategy would be for `Stdio` to take ownership of a `File` and wrap it | ||
| as a `Rc<File>`, allowing it to be "reused" in any number of redirections by | ||
| cloning the `Stdio` wrapper. A caller could try to regain ownership (via | ||
| `try_unwrap` on the internal wrapper), but they would have to (manually) ensure | ||
| all other `Stdio` clones are dropped. This design would also suffer from | ||
| potential deadlocks, making it by far the least ergonomic option. | ||
|
|
||
| # Unresolved questions | ||
|
|
||
| None at the moment. | ||
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 that this motivation section may want to be updated with the current state of the standard library because with the
FromRaw{Fd,Handle}implementations onStdioit'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.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.
Ah, I hadn't realized those changes had landed! I'll update the RFC to just focus on a high-level design