use std::{
ops::ControlFlow,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::{Duration, Instant},
};
use crate::{VmiContext, VmiEventResponse, VmiHandler, VmiOs, VmiSession};
enum TerminationState {
Running,
Interrupted(Instant),
Terminated,
}
pub struct VmiTerminationHandler<Handler> {
handler: Handler,
deadline: Option<Instant>,
termination_flag: Option<Arc<AtomicBool>>,
termination_timeout: Option<Duration>,
state: TerminationState,
}
impl<Handler> VmiTerminationHandler<Handler> {
pub fn new(handler: Handler) -> Self {
Self {
handler,
deadline: None,
termination_flag: None,
termination_timeout: None,
state: TerminationState::Running,
}
}
pub fn with_deadline(self, deadline: Instant) -> Self {
Self {
deadline: Some(deadline),
..self
}
}
pub fn with_termination_flag(self, termination_flag: Arc<AtomicBool>) -> Self {
Self {
termination_flag: Some(termination_flag),
..self
}
}
pub fn with_termination_timeout(self, termination_timeout: Duration) -> Self {
Self {
termination_timeout: Some(termination_timeout),
..self
}
}
fn check_termination<Os>(&mut self, session: &VmiSession<Os>)
where
Os: VmiOs,
Handler: VmiHandler<Os>,
{
if !matches!(self.state, TerminationState::Running) {
return;
}
let timed_out = self
.deadline
.is_some_and(|deadline| Instant::now() >= deadline);
let terminated = self
.termination_flag
.as_ref()
.is_some_and(|termination_flag| termination_flag.load(Ordering::Relaxed));
if timed_out || terminated {
self.interrupt(session);
}
}
fn interrupt<Os>(&mut self, session: &VmiSession<Os>)
where
Os: VmiOs,
Handler: VmiHandler<Os>,
{
self.state = match self.state {
TerminationState::Running => match self.handler.handle_interrupted(session) {
ControlFlow::Break(()) => TerminationState::Terminated,
ControlFlow::Continue(()) => TerminationState::Interrupted(Instant::now()),
},
TerminationState::Interrupted(_) | TerminationState::Terminated => {
TerminationState::Terminated
}
};
}
}
impl<Os, Handler> VmiHandler<Os> for VmiTerminationHandler<Handler>
where
Os: VmiOs,
Handler: VmiHandler<Os>,
{
type Output = Option<Handler::Output>;
fn handle_event(&mut self, vmi: VmiContext<Os>) -> VmiEventResponse<Os::Architecture> {
self.check_termination(&vmi);
self.handler.handle_event(vmi)
}
fn handle_timeout(&mut self, session: &VmiSession<Os>) {
self.check_termination(session);
self.handler.handle_timeout(session);
}
fn handle_interrupted(&mut self, session: &VmiSession<Os>) -> ControlFlow<()> {
self.interrupt(session);
match self.state {
TerminationState::Interrupted(_) => ControlFlow::Continue(()),
TerminationState::Running | TerminationState::Terminated => ControlFlow::Break(()),
}
}
fn cleanup(&mut self, session: &VmiSession<Os>) {
self.handler.cleanup(session);
}
fn poll(&mut self) -> Option<Self::Output> {
if let Some(output) = self.handler.poll() {
return Some(Some(output));
}
match self.state {
TerminationState::Running => None,
TerminationState::Interrupted(interrupted_at) => {
let termination_timeout = self.termination_timeout?;
if interrupted_at.elapsed() < termination_timeout {
return None;
}
tracing::warn!("handler did not finish in time");
Some(None)
}
TerminationState::Terminated => Some(None),
}
}
}