-
Notifications
You must be signed in to change notification settings - Fork 31
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
Add _timeout
methods for cmd to execute
#92
base: master
Are you sure you want to change the base?
Conversation
src/lib.rs
Outdated
} | ||
|
||
/// Run the command with a timeout and return its stdout as a string. Any trailing newline or carriage return will be trimmed. | ||
pub fn read_timeout(&self, timeout: Duration) -> Result<String> { |
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.
Rather than adding a separate xxx_timeout
method for each of operations we have, let's rather add a single .timeout()
builder method, a-la ignore_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.
Nice advice, it will be much neat.
src/lib.rs
Outdated
thread::spawn(move || { | ||
let output = child.wait_with_output(); | ||
let _ = sender.send(output); | ||
}); |
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 not quiet right --- we are "leaking" a thread handle here. As we are not manually .join
ing the thread, this means this thread can continue running past the point where output_impl
returns, which violates structured concurrency.
We should enforce the invariant that, by the time output_impl
returns, the child thread is .joined
.
We should also proactively .kill
the child process when the timeout happens. My understanding is that the current behavior is that the function retuns with an error after the timeout elapses, but the actual child process continues to execute, which is not what we want here.
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, you're absolutely right. Thank you for pointing it out. I'll find an appropriate place to .join
and .kill
this thread to prevent the "leaking".
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.
However, it seems that I hit the wall:
let out_res = if let Some(timeout) = self.data.timeout {
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let output = child.wait_with_output();
let _ = tx.send(output);
});
handle.join().unwrap();
match rx.recv_timeout(timeout) {
Ok(output) => output,
Err(err) => {
// FIXME: Kill the child, borrow of moved value: `child`
// child.kill();
return Err(Error::new_timeout(self, err));
}
}
} else {
child.wait_with_output()
};
Since .wait_with_output
need the child's ownership, when spawned, I can't use it to call .kill
anymore. I tried Arc way, but it still failed:
error[E0507]: cannot move out of dereference of MutexGuard<'_, Child>
@matklad Do you have any suggestion for this situation?
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.
Huh, that's nasty! This sadly might require us to essentially re-implement wait_with_output
, at least for the case where there's a timeout
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.
@Binlogo Saw this issue and thought I would mention this is basically the entire purpose of process_control.
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.
thanks for your information, it seems fit this situation, i'll take a look.
tests/it/timeout.rs
Outdated
#[test] | ||
fn test_read_stderr_timeout_failure() { | ||
let sh = Shell::new().unwrap(); | ||
let command = cmd!(sh, "sleep 5"); // Command that sleeps for 5 seconds |
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.
sleep
here is an utility from the OS, which is OS-specific and might not be present. For tests, we would want to use our own version of sleep
. See how the xecho
is implemented and used for similar tests, and use the same pattern for xsleep
.
Follow suggestion in #91 (comment)
Add timeout relative methods:
run_timeout
/read_timeout
/read_stderr_timeout
/output_timeout
.