use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
use anyhow::{Result, bail};
pub trait StopState {
fn clear_pending(&mut self);
fn has_pending_hit(&self) -> bool;
fn alive(&self) -> bool;
fn terminated(&self) -> bool {
false
}
fn stop_generation(&self) -> u64 {
0
}
fn action_generation(&self) -> u64 {
0
}
fn pending_action_generation(&self) -> u64 {
self.action_generation()
}
fn pending_is_unscoped(&self) -> bool {
false
}
}
pub fn wait_for_stop<S, F>(
state: &Arc<(Mutex<S>, Condvar)>,
action: F,
timeout: Duration,
) -> Result<String>
where
S: StopState,
F: FnOnce() -> Result<()>,
{
let baseline = {
let (lock, _) = &**state;
let mut guard = lock.lock().unwrap();
if guard.has_pending_hit()
&& (guard.pending_is_unscoped()
|| guard.pending_action_generation() >= guard.action_generation())
{
return Ok(String::new());
}
guard.clear_pending();
guard.stop_generation()
};
action()?;
let deadline = Instant::now() + timeout;
let (lock, cvar) = &**state;
let mut guard = lock.lock().unwrap();
while guard.alive()
&& (!guard.has_pending_hit()
|| guard.stop_generation() <= baseline
|| (guard.pending_action_generation() < guard.action_generation()
&& !guard.pending_is_unscoped()))
&& !guard.terminated()
{
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
bail!("timeout waiting for stopped event");
}
let r = cvar.wait_timeout(guard, remaining).unwrap();
guard = r.0;
}
let accepted = guard.has_pending_hit()
&& guard.stop_generation() > baseline
&& (guard.pending_is_unscoped()
|| guard.pending_action_generation() >= guard.action_generation());
if !accepted {
if guard.terminated() {
bail!("debugger terminated before reporting a stopped event");
}
if !guard.alive() {
bail!("debugger connection closed before reporting a stopped event");
}
}
Ok(String::new())
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
struct TestState {
pending: bool,
alive: bool,
generation: u64,
}
impl StopState for TestState {
fn clear_pending(&mut self) {
self.pending = false;
}
fn has_pending_hit(&self) -> bool {
self.pending
}
fn alive(&self) -> bool {
self.alive
}
fn stop_generation(&self) -> u64 {
self.generation
}
}
#[test]
fn a_stop_that_arrives_after_timeout_is_kept_for_the_next_action() {
let state = Arc::new((
Mutex::new(TestState {
pending: false,
alive: true,
generation: 0,
}),
Condvar::new(),
));
let late_state = state.clone();
let late = thread::spawn(move || {
thread::sleep(Duration::from_millis(20));
let (lock, cvar) = &*late_state;
let mut guard = lock.lock().unwrap();
guard.pending = true;
guard.generation = 1;
cvar.notify_all();
});
assert!(wait_for_stop(&state, || Ok(()), Duration::from_millis(5)).is_err());
late.join().unwrap();
let action_called = AtomicBool::new(false);
wait_for_stop(
&state,
|| {
action_called.store(true, Ordering::Relaxed);
Ok(())
},
Duration::from_millis(5),
)
.unwrap();
assert!(!action_called.load(Ordering::Relaxed));
}
}