Skip to content

Commit f0e99d7

Browse files
authored
Merge pull request #44 from rage/different-way-of-running
Implement a different way of running commands
2 parents 46427a2 + 9553137 commit f0e99d7

File tree

2 files changed

+103
-54
lines changed

2 files changed

+103
-54
lines changed

tmc-langs-framework/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ zip = "0.5"
1717
schemars = "0.7"
1818
once_cell = "1"
1919
nom = "5"
20+
shared_child = "0.3.4"
21+
os_pipe = "0.9.2"
2022

2123
[dev-dependencies]
2224
env_logger = "0.7"

tmc-langs-framework/src/command.rs

Lines changed: 101 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
//! Custom wrapper for Command that supports timeouts and contains custom error handling.
22
33
use crate::{error::CommandError, TmcError};
4-
use std::fmt;
4+
use os_pipe::pipe;
5+
#[cfg(unix)]
6+
use shared_child::unix::SharedChildExt;
7+
use shared_child::SharedChild;
58
use std::io::Read;
69
use std::ops::{Deref, DerefMut};
710
use std::path::PathBuf;
8-
use std::process::{Command, ExitStatus, Output, Stdio};
11+
use std::process::{Command, ExitStatus, Output};
12+
use std::sync::Arc;
913
use std::thread;
10-
use std::time::{Duration, Instant};
14+
use std::time::Duration;
15+
use std::{fmt, sync::Mutex};
1116

1217
// todo: collect args?
18+
#[derive(Debug)]
1319
pub struct TmcCommand {
1420
name: String,
1521
path: PathBuf,
@@ -106,59 +112,100 @@ impl TmcCommand {
106112
}
107113

108114
/// Waits with the given timeout. Sets stdout and stderr in order to capture them after erroring.
109-
pub fn wait_with_timeout(&mut self, timeout: Duration) -> Result<OutputWithTimeout, TmcError> {
110-
// spawn process and init timer
111-
let mut child = self
112-
.stdout(Stdio::piped())
113-
.stderr(Stdio::piped())
114-
.spawn()
115-
.map_err(|e| TmcError::Command(CommandError::Spawn(self.to_string(), e)))?;
116-
let timer = Instant::now();
117-
118-
loop {
119-
match child.try_wait().map_err(TmcError::Process)? {
120-
Some(_exit_status) => {
121-
// done, get output
122-
return child
123-
.wait_with_output()
124-
.map(OutputWithTimeout::Output)
125-
.map_err(|e| {
126-
if let std::io::ErrorKind::NotFound = e.kind() {
127-
TmcError::Command(CommandError::NotFound {
128-
name: self.name.clone(),
129-
path: self.path.clone(),
130-
source: e,
131-
})
132-
} else {
133-
TmcError::Command(CommandError::FailedToRun(self.to_string(), e))
134-
}
135-
});
115+
pub fn wait_with_timeout(mut self, timeout: Duration) -> Result<OutputWithTimeout, TmcError> {
116+
let name = self.name.clone();
117+
let path = self.path.clone();
118+
let self_string = self.to_string();
119+
let self_string2 = self.to_string();
120+
121+
let (mut stdout_reader, stdout_writer) = pipe().unwrap();
122+
let (mut stderr_reader, stderr_writer) = pipe().unwrap();
123+
124+
let (process_result, timed_out) = {
125+
let mut command = self.command;
126+
command.stdout(stdout_writer).stderr(stderr_writer);
127+
128+
let shared_child = SharedChild::spawn(&mut command)
129+
.map_err(|e| TmcError::Command(CommandError::Spawn(self_string, e)))?;
130+
let child_arc = Arc::new(shared_child);
131+
132+
let running = Arc::new(Mutex::new(true));
133+
let running_clone = running.clone();
134+
let timed_out = Arc::new(Mutex::new(false));
135+
136+
let child_arc_clone = child_arc.clone();
137+
let timed_out_clone = timed_out.clone();
138+
let _timeout_checker = thread::spawn(move || {
139+
thread::sleep(timeout);
140+
141+
if !running_clone.lock().unwrap().clone() {
142+
return;
136143
}
137-
None => {
138-
// still running, check timeout
139-
if timer.elapsed() > timeout {
140-
log::warn!("command {} timed out", self.name);
141-
// todo: cleaner method for killing
142-
child.kill().map_err(TmcError::Process)?;
143-
144-
let mut stdout = vec![];
145-
let mut stderr = vec![];
146-
let stdout_handle = child.stdout.as_mut().unwrap();
147-
let stderr_handle = child.stderr.as_mut().unwrap();
148-
stdout_handle
149-
.read_to_end(&mut stdout)
150-
.map_err(TmcError::ReadStdio)?;
151-
stderr_handle
152-
.read_to_end(&mut stderr)
153-
.map_err(TmcError::ReadStdio)?;
154-
return Ok(OutputWithTimeout::Timeout { stdout, stderr });
155-
}
156-
157-
// TODO: gradually increase sleep duration?
158-
thread::sleep(Duration::from_millis(100));
144+
let mut timed_out_handle = timed_out_clone.lock().unwrap();
145+
*timed_out_handle = true;
146+
147+
#[cfg(unix)]
148+
{
149+
// Ask process to terminate nicely
150+
let _res2 = child_arc_clone.send_signal(15);
151+
thread::sleep(Duration::from_millis(500));
152+
}
153+
// Force kill the process
154+
let _res = child_arc_clone.kill();
155+
});
156+
157+
let process_result = child_arc.wait();
158+
let mut running_handle = running.lock().unwrap();
159+
*running_handle = true;
160+
(process_result, timed_out)
161+
};
162+
163+
// Very important when using pipes: This parent process is still
164+
// holding its copies of the write ends, and we have to close them
165+
// before we read, otherwise the read end will never report EOF.
166+
// The block above drops everything unnecessary
167+
168+
let res = match process_result {
169+
Ok(exit_status) => {
170+
let mut stdout = vec![];
171+
let mut stderr = vec![];
172+
stdout_reader
173+
.read_to_end(&mut stdout)
174+
.map_err(TmcError::ReadStdio)?;
175+
stderr_reader
176+
.read_to_end(&mut stderr)
177+
.map_err(TmcError::ReadStdio)?;
178+
179+
Output {
180+
status: exit_status,
181+
stdout: stdout,
182+
stderr: stderr,
183+
}
184+
}
185+
Err(e) => {
186+
if let std::io::ErrorKind::NotFound = e.kind() {
187+
return Err(TmcError::Command(CommandError::NotFound {
188+
name: name,
189+
path: path,
190+
source: e,
191+
}));
192+
} else {
193+
return Err(TmcError::Command(CommandError::FailedToRun(
194+
self_string2,
195+
e,
196+
)));
159197
}
160198
}
199+
};
200+
201+
if timed_out.lock().unwrap().clone() {
202+
return Ok(OutputWithTimeout::Timeout {
203+
stdout: res.stdout,
204+
stderr: res.stderr,
205+
});
161206
}
207+
208+
return Ok(OutputWithTimeout::Output(res));
162209
}
163210
}
164211

@@ -175,7 +222,7 @@ impl DerefMut for TmcCommand {
175222
&mut self.command
176223
}
177224
}
178-
225+
#[derive(Debug)]
179226
pub enum OutputWithTimeout {
180227
Output(Output),
181228
Timeout { stdout: Vec<u8>, stderr: Vec<u8> },
@@ -207,7 +254,7 @@ mod test {
207254
let res = cmd.wait_with_timeout(Duration::from_millis(100)).unwrap();
208255
if let OutputWithTimeout::Timeout { .. } = res {
209256
} else {
210-
panic!("unexpected result");
257+
panic!(format!("Unexpected result {:?}", res));
211258
}
212259
}
213260
}

0 commit comments

Comments
 (0)