diff --git a/src/langserver/debugger/launched.rs b/src/langserver/debugger/launched.rs index 9056b85e..bf33e465 100644 --- a/src/langserver/debugger/launched.rs +++ b/src/langserver/debugger/launched.rs @@ -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, - mutex: Arc>, // TODO: AtomicBool instead? + mutex: Arc>, } impl Launched { pub fn new(seq: Arc, mut child: std::process::Child) -> std::io::Result { + 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); + } + } } }