-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Make process exit status more flexible to allow for handling when a child is terminated by signal #10109
Make process exit status more flexible to allow for handling when a child is terminated by signal #10109
Conversation
Maybe this should return an enum like: enum ProcessExit {
ExitOk, // status == 0, could be merged into ExitStatus.
ExitStatus(int),
ExitSignal(int)
} |
Working on the enum solution - it is significantly nicer. This would of course break any existing code that uses |
It's fine to break existing code, but I think that there should definitely be some tests accompanying this change. It's fine to only run the tests on some platforms (getting a forking/shell tests working on both windows/android is annoying), but this should not get regressed on. |
I have implemented the enum and added a few helpers to @alexcrichton I agree, so I added a test to |
Would it be possible to write a test that invokes itself with arguments that will raise a signal, e.g. fn main() {
let args = os::args();
if args.len() >= 2 && args[1] == "signal" {
// called like `./testname signal`, so raise a signal...
} else {
// work out the path to this executable.
let mut self_path = os::self_exe_path().unwrap();
self_path.push(args[0]);
let status = run::process_status(format!("{}", self_path.display()),
[~"signal"]);
assert_eq!(status, ExitSignal(/* whatever */));
}
} (This might be unreasonably complicated for a test.) |
} | ||
|
||
/// Helper to identify a ProcessExit enum value as a success. | ||
pub fn is_success(status: ProcessExit) -> bool { |
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.
Can these all be methods? i.e.
impl ProcessExit {
fn is_success(&self) -> bool { ... }
fn maybe_get_exit_code(&self) -> Option<int> { ... }
// etc
}
Hm ok reader the uv code, it looks like exec errors are reported in the callback (which is a little unfortunate). I also think that the current handling of the error codes is pretty wrong, and I think that the callback should get modified slightly. I think that the uv match term_signal {
0 => match exit_status {
n if n < 0 => ExitExecError(UvError(n)),
n => ExitStatus(n),
},
n => ExitSignal(n),
} I'm not entirely certain if you can have a clean negative exit status, but I that this will correctly propagate the uv error. Note that |
We actually have a raw Will work on fixing up these things - the utility functions for example should've been methods from the very beginning. Just seeking some clarification @alexcrichton when you say the There's a test in |
Right now the function in |
Updated with input from review and rebased onto current master with the libuv crate move. No local regressions with a Still not totally sure on the I am also uncertain about the updated code to handle problems running @huonw - RE the test that invokes itself. Sure, I can look into doing that. Is there any advantage to doing it that way over the current test, which sends itself a SIGHUP via bash? |
It is more platform independent than calling out to sh; i.e. it should work on any platform that supports running external processes, whereas the sh one only works when sh is around. |
if !ProcRes.status.matches_exit_status(RUST_ERR) { | ||
let exit_code = ProcRes.status.exit_code(); | ||
let term_signal = ProcRes.status.term_signal(); | ||
if exit_code.is_some() { |
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 seems to be a pretty common switch, and it looks like the case of a fork error is commonly forgotten as well. I think that a good way to handle this might be to have a to_str()
or fmt::Default
implementation for ProcessExit
which becomes one of:
error code: 100
signal: 10
exec error: failed to allocate memory
And then error messages like this could be format!("failure produced {}", status)
This is really awesome, thanks so much for this excellent work! I think the only real remaining bump is Also, were you able to figure out what happened if you spawned a process of a nonexistent program? I would expect that to return an |
Working on these now. @alexcrichton had a quick look and the pattern matching against an IoError requires a move - I really haven't looked too closely yet though while I do the formatter and such. @huonw fair point! I have no idea what happens when you segfault on Windows with respect to exit codes or "signals" - and I don't have a Windows machine handy. I could do up a test with this PR that works on unixy systems, and see what it does when it hits CI on Windows? |
@alexcrichton the latest commit has everything bar the IoError change. This includes modifying the existing test in @huonw I have added a new test that uses more or less the code you suggested, and checks for termination by signal. I have specifically avoided checking the exact value of the signal to avoid having to deal with system specifics (eg, SIGBUS vs SIGSEGV vs Windows), resorting to only |
// option. this file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
// compile-flags: --test |
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 have a feeling this isn't correct: doesn't this attempt to compile the #[test]
in this file (i.e. all zero of them :P ) into a testsuite, and should just be removed?
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.
Oops!
} else { | ||
// Work out the path to this executable. | ||
let mut self_path = os::self_exe_path().unwrap(); | ||
self_path.push(args[0]); |
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.
Isn't args[0]
equivalent to self_path
? It seems like this push
is simply overriding what was previously there.
With the latest commit, This is because I have not yet made I feel like this code is a lot hackier as a result of trying to make this work and I don't like it as much, but I've put it up as an RFC to make sure I'm going about things the right way. |
cc_prog, prog.status)); | ||
|
||
if !prog.status.success() { | ||
sess.err(format!("building with `{}` failed: {}", cc_prog, prog.status)); |
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 probably still say linking with
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.
Oops! Missed that in the diff before I pushed. Cheers.
Pushed current work. @alexcrichton just waiting now on word about libuv (and watching the related issue over there). |
I'm upgrading libuv as part of #10321, after that lands this should be ready for a rebase. |
Aha! I've finally landed 10321, so this should be ready for a rebase now. |
@alexcrichton rebased and should be good to go (the builders might disagree, though I've done tests). |
@@ -544,7 +545,7 @@ fn frob_source_file(workspace: &Path, pkgid: &PkgId, filename: &str) { | |||
do io::io_error::cond.trap(|e| { | |||
cond.raise((p.clone(), format!("Bad path: {}", e.desc))); | |||
}).inside { | |||
let mut w = File::open_mode(p, io::Append, io::Write); | |||
let mut w = p.open_writer(io::Append); |
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 don't think that this function exists any more
…cess for getting process termination status, or the signal that terminated a process. A test has been added to rtio-processes.rs to ensure signal termination is picked up correctly.
…mination-by-signal, r=alexcrichton The UvProcess exit callback is called with a zero exit status and non-zero termination signal when a child is terminated by a signal. If a parent checks only the exit status (for example, only checks the return value from `wait()`), it may believe the process completed successfully when it actually failed. Helpers for common use-cases are in `std::rt::io::process`. Should resolve #10062.
let args = os::args(); | ||
if args.len() >= 2 && args[1] == ~"signal" { | ||
// Raise a segfault. | ||
unsafe { *(0 as *mut int) = 0; } |
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 is UB and has remained unfixed on master, the address should be 1
or something else, not NULL.
Might work on windows too then.
…chton Fix run-pass/signal-exit-status to not trigger UB by writing to NULL. `run-pass/signal-exit-status` has had UB (NULL dereference) since it was introduced in rust-lang#10109. Fixes the test failure found by @camlorn while running under Windows Subsystem for Linux.
Fix FP in needless_return when using yeet Fixes rust-lang#9947 changelog: Fix: [`needless_return`]: don't lint when using `do yeet` rust-lang#10109
The UvProcess exit callback is called with a zero exit status and non-zero termination signal when a child is terminated by a signal.
If a parent checks only the exit status (for example, only checks the return value from
wait()
), it may believe the process completed successfully when it actually failed.Helpers for common use-cases are in
std::rt::io::process
.Should resolve #10062.