1
0
forked from ROMEO/nexosim

Add support for simulation timeouts

This commit is contained in:
Serge Barral
2024-11-08 15:15:28 +01:00
parent c6fd4d90c4
commit e6901386cf
17 changed files with 468 additions and 153 deletions

View File

@ -145,7 +145,7 @@ use std::time::Duration;
use recycle_box::{coerce_box, RecycleBox};
use crate::channel::ChannelObserver;
use crate::executor::{Executor, ExecutorError};
use crate::executor::{Executor, ExecutorError, Signal};
use crate::model::{BuildContext, Context, Model, ProtoModel};
use crate::ports::{InputFn, ReplierFn};
use crate::time::{AtomicTime, Clock, MonotonicTime};
@ -195,6 +195,7 @@ pub struct Simulation {
scheduler_queue: Arc<Mutex<SchedulerQueue>>,
time: AtomicTime,
clock: Box<dyn Clock>,
timeout: Duration,
observers: Vec<(String, Box<dyn ChannelObserver>)>,
is_terminated: bool,
}
@ -206,6 +207,7 @@ impl Simulation {
scheduler_queue: Arc<Mutex<SchedulerQueue>>,
time: AtomicTime,
clock: Box<dyn Clock + 'static>,
timeout: Duration,
observers: Vec<(String, Box<dyn ChannelObserver>)>,
) -> Self {
Self {
@ -213,11 +215,26 @@ impl Simulation {
scheduler_queue,
time,
clock,
timeout,
observers,
is_terminated: false,
}
}
/// Sets a timeout for each simulation step.
///
/// The timeout corresponds to the maximum wall clock time allocated for the
/// completion of a single simulation step before an
/// [`ExecutionError::Timeout`] error is raised.
///
/// A null duration disables the timeout, which is the default behavior.
///
/// See also [`SimInit::set_timeout`].
#[cfg(not(target_family = "wasm"))]
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
/// Returns the current simulation time.
pub fn time(&self) -> MonotonicTime {
self.time.read()
@ -362,7 +379,7 @@ impl Simulation {
return Err(ExecutionError::Terminated);
}
self.executor.run().map_err(|e| match e {
self.executor.run(self.timeout).map_err(|e| match e {
ExecutorError::Deadlock => {
self.is_terminated = true;
let mut deadlock_info = Vec::new();
@ -378,6 +395,11 @@ impl Simulation {
ExecutionError::Deadlock(deadlock_info)
}
ExecutorError::Timeout => {
self.is_terminated = true;
ExecutionError::Timeout
}
})
}
@ -544,8 +566,10 @@ pub enum ExecutionError {
/// The query was invalid and did not obtain a response.
BadQuery,
/// The simulation has been terminated due to an earlier deadlock, model
/// error or model panic.
/// error, model panic or timeout.
Terminated,
/// The simulation step has failed to complete within the allocated time.
Timeout,
}
impl fmt::Display for ExecutionError {
@ -593,6 +617,7 @@ impl fmt::Display for ExecutionError {
}
Self::BadQuery => f.write_str("the query did not return any response; maybe the target model was not added to the simulation?"),
Self::Terminated => f.write_str("the simulation has been terminated"),
Self::Timeout => f.write_str("the simulation step has failed to complete within the allocated time"),
}
}
}
@ -653,19 +678,22 @@ pub(crate) fn add_model<P: ProtoModel>(
name: String,
scheduler: Scheduler,
executor: &Executor,
abort_signal: &Signal,
) {
#[cfg(feature = "tracing")]
let span = tracing::span!(target: env!("CARGO_PKG_NAME"), tracing::Level::INFO, "model", name);
let context = Context::new(name, LocalScheduler::new(scheduler, mailbox.address()));
let build_context = BuildContext::new(&mailbox, &context, executor);
let build_context = BuildContext::new(&mailbox, &context, executor, abort_signal);
let model = model.build(&build_context);
let mut receiver = mailbox.0;
let abort_signal = abort_signal.clone();
let fut = async move {
let mut model = model.init(&context).await.0;
while receiver.recv(&mut model, &context).await.is_ok() {}
while !abort_signal.is_set() && receiver.recv(&mut model, &context).await.is_ok() {}
};
#[cfg(feature = "tracing")]