Codify launched subprocess state machine

This commit is contained in:
Tad Hardesty 2019-11-11 19:14:53 -08:00
parent 93cde34a78
commit a2eece17a8

View file

@ -5,41 +5,67 @@ use std::sync::{Arc, Mutex};
use super::SequenceNumber;
use super::dap_types::{ExitedEvent, TerminatedEvent};
// active --kill--> killed: emit Terminated, send SIGKILL
// active --detach--> detached: emit Terminated
// active --exit--> exited: emit Terminated + Exited
// killed --exit--> exited: emit Exited
// detached --exit--> exited: emit Exited
// exited --kill--> exited: no-op
// exited --detach--> exited: no-op
#[derive(Copy, Clone, Debug)]
enum State {
Active,
Killed,
Detached,
Exited,
}
// TODO: This code currently emits the Terminated event in order to cover for
// no actual debugging taking place. When debugging is implemented, that event
// should be moved to be
pub struct Launched {
handle: raw::Handle,
seq: Arc<SequenceNumber>,
mutex: Arc<Mutex<bool>>, // TODO: AtomicBool instead?
mutex: Arc<Mutex<State>>,
}
impl Launched {
pub fn new(seq: Arc<SequenceNumber>, mut child: std::process::Child) -> std::io::Result<Launched> {
let mutex = Arc::new(Mutex::new(State::Active));
let handle = raw::from(&child);
let seq2 = seq.clone();
let mutex = Arc::new(Mutex::new(false));
let mutex2 = mutex.clone();
std::thread::Builder::new()
.name("launched debuggee manager thread".to_owned())
.spawn(move || {
eprintln!("[launched] child started");
let code = match child.wait() {
let wait = child.wait();
// lock as soon as possible to minimize risk of shenanigans
let mut state = mutex2.lock().expect("launched mutex poisoned");
let code = match wait {
Ok(status) => {
let code = status.code();
eprintln!("[launched] child exited with code {:?}", code);
eprintln!("[launched] child exited in state {:?} with code {:?}", *state, code);
code.unwrap_or(-1)
}
Err(e) => {
eprintln!("[launched] wait() errored: {:?}", e);
Err(err) => {
eprintln!("[launched] wait() errored in state {:?}: {:?}", *state, err);
-1
}
};
*mutex2.lock().unwrap() = true;
seq2.issue_event(TerminatedEvent::default());
if let State::Active = *state {
seq2.issue_event(TerminatedEvent::default());
}
*state = State::Exited;
seq2.issue_event(ExitedEvent {
exitCode: code as i64,
});
eprintln!("launched - exited issued");
})?;
Ok(Launched {
handle,
seq,
@ -48,22 +74,36 @@ impl Launched {
}
pub fn kill(self) -> std::io::Result<()> {
eprintln!("launched - kill called");
if *self.mutex.lock().unwrap() {
// don't kill if the wait() has completed
eprintln!("launched - kill short circuiting");
return Ok(());
}
eprintln!("[launched] killing child process");
match unsafe { raw::kill(self.handle) } {
true => Ok(()),
false => Err(std::io::Error::last_os_error()),
let mut state = self.mutex.lock().expect("launched mutex poisoned");
match *state {
State::Active => {
eprintln!("[launched] killing child process");
self.seq.issue_event(TerminatedEvent::default());
*state = State::Killed;
match unsafe { raw::kill(self.handle) } {
true => Ok(()),
false => Err(std::io::Error::last_os_error()),
}
}
other => {
eprintln!("[launched] kill no-op in state {:?}", other);
Ok(())
}
}
}
pub fn detach(self) {
eprintln!("[launched] detaching softly");
self.seq.issue_event(TerminatedEvent::default());
let mut state = self.mutex.lock().expect("launched mutex poisoned");
match *state {
State::Active => {
eprintln!("[launched] detaching from child process");
self.seq.issue_event(TerminatedEvent::default());
*state = State::Detached;
}
other => {
eprintln!("[launched] detach no-op in state {:?}", other);
}
}
}
}