use serde::{Deserialize, Serialize};
use nexosim::model::Model;
use nexosim::ports::{EventSource, Output, QuerySource, Requestor};
use nexosim::simulation::{DeadlockInfo, ExecutionError, Mailbox, SimInit};
use nexosim::time::MonotonicTime;
const MT_NUM_THREADS: usize = 4;
#[derive(Default, Deserialize, Serialize)]
struct TestModel {
output: Output<()>,
requestor: Requestor<(), ()>,
}
#[Model]
impl TestModel {
async fn activate_output(&mut self) {
self.output.send(()).await;
}
async fn activate_requestor(&mut self) {
let _ = self.requestor.send(()).await;
}
}
fn deadlock_on_mailbox_overflow(num_threads: usize) {
const MODEL_NAME: &str = "testmodel";
const MAILBOX_SIZE: usize = 5;
let mut model = TestModel::default();
let mbox = Mailbox::with_capacity(MAILBOX_SIZE);
let mut bench = SimInit::with_num_threads(num_threads);
let activate_output = EventSource::new()
.connect(TestModel::activate_output, &mbox)
.register(&mut bench);
model.output.connect(TestModel::activate_output, &mbox);
model.output.connect(TestModel::activate_output, &mbox);
let t0 = MonotonicTime::EPOCH;
let mut simu = bench.add_model(model, mbox, MODEL_NAME).init(t0).unwrap();
match simu.process_event(&activate_output, ()) {
Err(ExecutionError::Deadlock(deadlock_info)) => {
assert_eq!(deadlock_info.len(), 1);
assert_eq!(
deadlock_info[0],
DeadlockInfo {
model: MODEL_NAME.into(),
mailbox_size: MAILBOX_SIZE
}
)
}
_ => panic!("deadlock not detected"),
}
}
fn deadlock_on_query_loopback(num_threads: usize) {
const MODEL_NAME: &str = "testmodel";
let mut model = TestModel::default();
let mbox = Mailbox::new();
model
.requestor
.connect(TestModel::activate_requestor, &mbox);
let mut bench = SimInit::with_num_threads(num_threads);
let query = QuerySource::new()
.connect(TestModel::activate_requestor, &mbox)
.register(&mut bench);
let t0 = MonotonicTime::EPOCH;
let mut simu = bench.add_model(model, mbox, MODEL_NAME).init(t0).unwrap();
match simu.process_query(&query, ()) {
Err(ExecutionError::Deadlock(deadlock_info)) => {
assert_eq!(deadlock_info.len(), 1);
assert_eq!(
deadlock_info[0],
DeadlockInfo {
model: MODEL_NAME.into(),
mailbox_size: 1,
}
);
}
_ => panic!("deadlock not detected"),
}
}
fn deadlock_on_transitive_query_loopback(num_threads: usize) {
const MODEL1_NAME: &str = "testmodel1";
const MODEL2_NAME: &str = "testmodel2";
let mut model1 = TestModel::default();
let mut model2 = TestModel::default();
let mbox1 = Mailbox::new();
let mbox2 = Mailbox::new();
model1
.requestor
.connect(TestModel::activate_requestor, &mbox2);
model2
.requestor
.connect(TestModel::activate_requestor, &mbox1);
let mut bench = SimInit::with_num_threads(num_threads);
let query = QuerySource::new()
.connect(TestModel::activate_requestor, &mbox1)
.register(&mut bench);
let t0 = MonotonicTime::EPOCH;
let mut simu = bench
.add_model(model1, mbox1, MODEL1_NAME)
.add_model(model2, mbox2, MODEL2_NAME)
.init(t0)
.unwrap();
match simu.process_query(&query, ()) {
Err(ExecutionError::Deadlock(deadlock_info)) => {
assert_eq!(deadlock_info.len(), 1);
assert_eq!(
deadlock_info[0],
DeadlockInfo {
model: MODEL1_NAME.into(),
mailbox_size: 1,
}
);
}
_ => panic!("deadlock not detected"),
}
}
fn deadlock_on_multiple_query_loopback(num_threads: usize) {
const MODEL0_NAME: &str = "testmodel0";
const MODEL1_NAME: &str = "testmodel1";
const MODEL2_NAME: &str = "testmodel2";
let mut model0 = TestModel::default();
let mut model1 = TestModel::default();
let mut model2 = TestModel::default();
let mbox0 = Mailbox::new();
let mbox1 = Mailbox::new();
let mbox2 = Mailbox::new();
model0
.requestor
.connect(TestModel::activate_requestor, &mbox1);
model0
.requestor
.connect(TestModel::activate_requestor, &mbox2);
model1
.requestor
.connect(TestModel::activate_requestor, &mbox1);
model2
.requestor
.connect(TestModel::activate_requestor, &mbox2);
let mut bench = SimInit::with_num_threads(num_threads);
let query = QuerySource::new()
.connect(TestModel::activate_requestor, &mbox0)
.register(&mut bench);
let t0 = MonotonicTime::EPOCH;
let mut simu = bench
.add_model(model0, mbox0, MODEL0_NAME)
.add_model(model1, mbox1, MODEL1_NAME)
.add_model(model2, mbox2, MODEL2_NAME)
.init(t0)
.unwrap();
match simu.process_query(&query, ()) {
Err(ExecutionError::Deadlock(deadlock_info)) => {
assert_eq!(deadlock_info.len(), 2);
assert_eq!(
deadlock_info[0],
DeadlockInfo {
model: MODEL1_NAME.into(),
mailbox_size: 1,
}
);
assert_eq!(
deadlock_info[1],
DeadlockInfo {
model: MODEL2_NAME.into(),
mailbox_size: 1,
}
);
}
_ => panic!("deadlock not detected"),
}
}
#[test]
fn deadlock_on_mailbox_overflow_st() {
deadlock_on_mailbox_overflow(1);
}
#[test]
fn deadlock_on_mailbox_overflow_mt() {
deadlock_on_mailbox_overflow(MT_NUM_THREADS);
}
#[test]
fn deadlock_on_query_loopback_st() {
deadlock_on_query_loopback(1);
}
#[test]
fn deadlock_on_query_loopback_mt() {
deadlock_on_query_loopback(MT_NUM_THREADS);
}
#[test]
fn deadlock_on_transitive_query_loopback_st() {
deadlock_on_transitive_query_loopback(1);
}
#[test]
fn deadlock_on_transitive_query_loopback_mt() {
deadlock_on_transitive_query_loopback(MT_NUM_THREADS);
}
#[test]
fn deadlock_on_multiple_query_loopback_st() {
deadlock_on_multiple_query_loopback(1);
}
#[test]
fn deadlock_on_multiple_query_loopback_mt() {
deadlock_on_multiple_query_loopback(MT_NUM_THREADS);
}