Skip to main content

vmi_core/handler/
termination.rs

1use std::{
2    ops::ControlFlow,
3    sync::{
4        Arc,
5        atomic::{AtomicBool, Ordering},
6    },
7    time::{Duration, Instant},
8};
9
10use crate::{VmiContext, VmiEventResponse, VmiHandler, VmiOs, VmiSession};
11
12/// Termination state of a [`VmiTerminationHandler`].
13enum TerminationState {
14    /// The handler has not been interrupted.
15    Running,
16
17    /// The handler was interrupted and is given time to finish.
18    ///
19    /// The [`Instant`] records when the handler was interrupted. The
20    /// termination timeout counts from it.
21    Interrupted(Instant),
22
23    /// The handler was interrupted and is no longer waited for. The event
24    /// loop ends at the next poll.
25    Terminated,
26}
27
28/// Handler that interrupts another handler at a deadline or once a
29/// termination flag is set.
30///
31/// [`VmiHandlerExt`] adds it to any handler, for example with
32/// [`VmiHandlerExt::with_deadline`]. The wrapped handler is interrupted
33/// through [`VmiHandler::handle_interrupted`], as if by a signal.
34///
35/// If the guest causes no events, the event loop wakes up only once per
36/// wait timeout. The deadline, the termination flag and the termination
37/// timeout can then take effect up to one wait timeout late.
38///
39/// The output is `None` if the event loop ended without the output of the
40/// wrapped handler, after it was interrupted.
41///
42/// [`VmiHandlerExt`]: crate::VmiHandlerExt
43/// [`VmiHandlerExt::with_deadline`]: crate::VmiHandlerExt::with_deadline
44pub struct VmiTerminationHandler<Handler> {
45    /// Handler that processes the events.
46    handler: Handler,
47
48    /// Optional time at which the handler is interrupted.
49    deadline: Option<Instant>,
50
51    /// Optional flag that interrupts the handler once it becomes `true`.
52    termination_flag: Option<Arc<AtomicBool>>,
53
54    /// Optional time the handler has to finish once interrupted.
55    termination_timeout: Option<Duration>,
56
57    /// Current termination state of the handler.
58    state: TerminationState,
59}
60
61impl<Handler> VmiTerminationHandler<Handler> {
62    /// Creates a new termination handler with the given handler.
63    pub fn new(handler: Handler) -> Self {
64        Self {
65            handler,
66            deadline: None,
67            termination_flag: None,
68            termination_timeout: None,
69            state: TerminationState::Running,
70        }
71    }
72
73    /// Sets the time at which the handler is interrupted.
74    pub fn with_deadline(self, deadline: Instant) -> Self {
75        Self {
76            deadline: Some(deadline),
77            ..self
78        }
79    }
80
81    /// Installs a flag that interrupts the handler once it becomes `true`.
82    pub fn with_termination_flag(self, termination_flag: Arc<AtomicBool>) -> Self {
83        Self {
84            termination_flag: Some(termination_flag),
85            ..self
86        }
87    }
88
89    /// Sets the time the handler has to finish once interrupted, by a
90    /// signal, the deadline or the termination flag. When the time runs
91    /// out, the event loop ends without the output of the handler.
92    pub fn with_termination_timeout(self, termination_timeout: Duration) -> Self {
93        Self {
94            termination_timeout: Some(termination_timeout),
95            ..self
96        }
97    }
98
99    /// Interrupts the handler once the deadline passes or the termination
100    /// flag is set.
101    fn check_termination<Os>(&mut self, session: &VmiSession<Os>)
102    where
103        Os: VmiOs,
104        Handler: VmiHandler<Os>,
105    {
106        if !matches!(self.state, TerminationState::Running) {
107            return;
108        }
109
110        let timed_out = self
111            .deadline
112            .is_some_and(|deadline| Instant::now() >= deadline);
113
114        let terminated = self
115            .termination_flag
116            .as_ref()
117            .is_some_and(|termination_flag| termination_flag.load(Ordering::Relaxed));
118
119        if timed_out || terminated {
120            self.interrupt(session);
121        }
122    }
123
124    /// Interrupts the handler. If it was interrupted before, stops waiting
125    /// for it.
126    fn interrupt<Os>(&mut self, session: &VmiSession<Os>)
127    where
128        Os: VmiOs,
129        Handler: VmiHandler<Os>,
130    {
131        self.state = match self.state {
132            TerminationState::Running => match self.handler.handle_interrupted(session) {
133                ControlFlow::Break(()) => TerminationState::Terminated,
134                ControlFlow::Continue(()) => TerminationState::Interrupted(Instant::now()),
135            },
136            TerminationState::Interrupted(_) | TerminationState::Terminated => {
137                TerminationState::Terminated
138            }
139        };
140    }
141}
142
143impl<Os, Handler> VmiHandler<Os> for VmiTerminationHandler<Handler>
144where
145    Os: VmiOs,
146    Handler: VmiHandler<Os>,
147{
148    type Output = Option<Handler::Output>;
149
150    fn handle_event(&mut self, vmi: VmiContext<Os>) -> VmiEventResponse<Os::Architecture> {
151        self.check_termination(&vmi);
152        self.handler.handle_event(vmi)
153    }
154
155    fn handle_timeout(&mut self, session: &VmiSession<Os>) {
156        self.check_termination(session);
157        self.handler.handle_timeout(session);
158    }
159
160    fn handle_interrupted(&mut self, session: &VmiSession<Os>) -> ControlFlow<()> {
161        self.interrupt(session);
162
163        match self.state {
164            TerminationState::Interrupted(_) => ControlFlow::Continue(()),
165            TerminationState::Running | TerminationState::Terminated => ControlFlow::Break(()),
166        }
167    }
168
169    fn cleanup(&mut self, session: &VmiSession<Os>) {
170        self.handler.cleanup(session);
171    }
172
173    fn poll(&mut self) -> Option<Self::Output> {
174        if let Some(output) = self.handler.poll() {
175            return Some(Some(output));
176        }
177
178        match self.state {
179            TerminationState::Running => None,
180            TerminationState::Interrupted(interrupted_at) => {
181                let termination_timeout = self.termination_timeout?;
182                if interrupted_at.elapsed() < termination_timeout {
183                    return None;
184                }
185
186                tracing::warn!("handler did not finish in time");
187                Some(None)
188            }
189            TerminationState::Terminated => Some(None),
190        }
191    }
192}