1
0
forked from ROMEO/nexosim

Add test and improve example

This commit is contained in:
Jaŭhien Piatlicki 2024-05-06 15:31:50 +02:00
parent 287b3b713c
commit 02eec1b277
4 changed files with 129 additions and 42 deletions

View File

@ -14,6 +14,7 @@ on:
- 'asynchronix/src/ports/output/broadcaster/**'
- 'asynchronix/src/ports/source/broadcaster.rs'
- 'asynchronix/src/ports/source/broadcaster/**'
- 'asynchronix/src/util/cached_rw_lock.rs'
- 'asynchronix/src/util/slot.rs'
- 'asynchronix/src/util/sync_cell.rs'

View File

@ -1,7 +1,8 @@
#[cfg(asynchronix_loom)]
#[allow(unused_imports)]
pub(crate) mod sync {
pub(crate) use loom::sync::{Arc, Mutex};
pub(crate) use loom::sync::{Arc, LockResult, Mutex, MutexGuard};
pub(crate) use std::sync::PoisonError;
pub(crate) mod atomic {
pub(crate) use loom::sync::atomic::{
@ -12,7 +13,7 @@ pub(crate) mod sync {
#[cfg(not(asynchronix_loom))]
#[allow(unused_imports)]
pub(crate) mod sync {
pub(crate) use std::sync::{Arc, Mutex};
pub(crate) use std::sync::{Arc, LockResult, Mutex, MutexGuard, PoisonError};
pub(crate) mod atomic {
pub(crate) use std::sync::atomic::{

View File

@ -19,33 +19,55 @@
//!
//! #### Example
//!
//! The outputs in this example are clones of each other and remain therefore
//! always connected to the same inputs. For an example usage of outputs cloning
//! in submodels assemblies, see the [`assembly example`][assembly].
//! This example demonstrates a submodel inside a parent model. The output of
//! the submodel is a clone of the parent model output. Both outputs remain
//! therefore always connected to the same inputs.
//!
//! For a more comprehensive example demonstrating output cloning in submodels
//! assemblies, see the [`assembly example`][assembly].
//!
//! [assembly]:
//! https://github.com/asynchronics/asynchronix/tree/main/asynchronix/examples/assembly.rs
//!
//! ```
//! use asynchronix::model::Model;
//! use asynchronix::model::{Model, SetupContext};
//! use asynchronix::ports::Output;
//! use asynchronix::simulation::Mailbox;
//!
//! pub struct MyModel {
//! pub output_a: Output<u64>,
//! pub output_b: Output<u64>,
//! pub struct ChildModel {
//! pub output: Output<u64>,
//! }
//!
//! impl MyModel {
//! impl ChildModel {
//! pub fn new() -> Self {
//! let output: Output<_> = Default::default();
//! Self {
//! output_a: output.clone(),
//! output_b: output,
//! output: Default::default(),
//! }
//! }
//! }
//!
//! impl Model for MyModel {}
//! impl Model for ChildModel {}
//!
//! pub struct ParentModel {
//! pub output: Output<u64>,
//! }
//!
//! impl ParentModel {
//! pub fn new() -> Self {
//! Self {
//! output: Default::default(),
//! }
//! }
//! }
//!
//! impl Model for ParentModel {
//! fn setup(&mut self, setup_context: &SetupContext<Self>) {
//! let mut child = ChildModel::new();
//! let child_mbox = Mailbox::new();
//! child.output = self.output.clone();
//! setup_context.add_model(child, child_mbox);
//! }
//! }
//! ```
mod input;

View File

@ -1,6 +1,7 @@
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, LockResult, Mutex, MutexGuard, PoisonError};
use crate::loom_exports::sync::atomic::{AtomicUsize, Ordering};
use crate::loom_exports::sync::{Arc, LockResult, Mutex, MutexGuard, PoisonError};
/// A cached read-write lock.
///
@ -13,10 +14,9 @@ use std::sync::{Arc, LockResult, Mutex, MutexGuard, PoisonError};
/// synchronization.
#[derive(Clone)]
pub(crate) struct CachedRwLock<T: Clone> {
local: T,
local_epoch: usize,
shared: Arc<Mutex<T>>,
epoch: Arc<AtomicUsize>,
value: T,
epoch: usize,
shared: Arc<Shared<T>>,
}
impl<T: Clone> CachedRwLock<T> {
@ -24,61 +24,63 @@ impl<T: Clone> CachedRwLock<T> {
pub(crate) fn new(t: T) -> Self {
let shared = t.clone();
Self {
local: t,
local_epoch: 0,
shared: Arc::new(Mutex::new(shared)),
epoch: Arc::new(AtomicUsize::new(0)),
value: t,
epoch: 0,
shared: Arc::new(Shared {
value: Mutex::new(shared),
epoch: AtomicUsize::new(0),
}),
}
}
/// Gives access to the local cache without synchronization.
pub(crate) fn read_unsync(&self) -> &T {
&self.local
&self.value
}
/// Synchronizes the local cache if it is behind the shared data and gives
/// access to it.
#[allow(dead_code)]
pub(crate) fn read(&mut self) -> LockResult<&T> {
if self.epoch.load(Ordering::Relaxed) != self.local_epoch {
match self.shared.lock() {
if self.shared.epoch.load(Ordering::Relaxed) != self.epoch {
match self.shared.value.lock() {
LockResult::Ok(shared) => {
self.local = shared.clone();
self.local_epoch = self.epoch.load(Ordering::Relaxed)
self.value = shared.clone();
self.epoch = self.shared.epoch.load(Ordering::Relaxed)
}
LockResult::Err(_) => return LockResult::Err(PoisonError::new(&self.local)),
LockResult::Err(_) => return LockResult::Err(PoisonError::new(&self.value)),
}
}
LockResult::Ok(&self.local)
LockResult::Ok(&self.value)
}
/// Gives write access to the local cache without synchronization so it can
/// be used as a scratchpad.
#[allow(dead_code)]
pub(crate) fn write_scratchpad_unsync(&mut self) -> &mut T {
&mut self.local
&mut self.value
}
/// Synchronizes the local cache if it is behind the shared data and gives
/// write access to it so it can be used as a scratchpad.
pub(crate) fn write_scratchpad(&mut self) -> LockResult<&mut T> {
if self.epoch.load(Ordering::Relaxed) != self.local_epoch {
match self.shared.lock() {
if self.shared.epoch.load(Ordering::Relaxed) != self.epoch {
match self.shared.value.lock() {
LockResult::Ok(shared) => {
self.local = shared.clone();
self.local_epoch = self.epoch.load(Ordering::Relaxed)
self.value = shared.clone();
self.epoch = self.shared.epoch.load(Ordering::Relaxed)
}
LockResult::Err(_) => return LockResult::Err(PoisonError::new(&mut self.local)),
LockResult::Err(_) => return LockResult::Err(PoisonError::new(&mut self.value)),
}
}
LockResult::Ok(&mut self.local)
LockResult::Ok(&mut self.value)
}
/// Acquires a write lock on the shared data.
pub(crate) fn write(&mut self) -> LockResult<CachedRwLockWriteGuard<'_, T>> {
let guard = self.shared.lock();
let epoch = self.epoch.load(Ordering::Relaxed) + 1;
self.epoch.store(epoch, Ordering::Relaxed);
let guard = self.shared.value.lock();
let epoch = self.shared.epoch.load(Ordering::Relaxed) + 1;
self.shared.epoch.store(epoch, Ordering::Relaxed);
match guard {
LockResult::Ok(shared) => LockResult::Ok(CachedRwLockWriteGuard { guard: shared }),
@ -89,6 +91,11 @@ impl<T: Clone> CachedRwLock<T> {
}
}
struct Shared<T> {
epoch: AtomicUsize,
value: Mutex<T>,
}
/// Write guard.
///
/// The lock is released when the guard is dropped.
@ -109,3 +116,59 @@ impl<T: Clone> DerefMut for CachedRwLockWriteGuard<'_, T> {
&mut self.guard
}
}
#[cfg(all(test, asynchronix_loom))]
mod tests {
use super::*;
use loom::model::Builder;
use loom::thread;
#[test]
fn loom_cached_rw_lock_write() {
const DEFAULT_PREEMPTION_BOUND: usize = 4;
const ITERATIONS_NUMBER: usize = 5;
let mut builder = Builder::new();
if builder.preemption_bound.is_none() {
builder.preemption_bound = Some(DEFAULT_PREEMPTION_BOUND);
}
builder.check(move || {
let mut writer0: CachedRwLock<usize> = CachedRwLock::new(0);
let mut writer1 = writer0.clone();
let mut reader = writer0.clone();
let th_w = thread::spawn(move || {
for _ in 0..ITERATIONS_NUMBER {
let mut guard = writer0.write().unwrap();
*guard = *guard + 1;
}
});
let th_r = thread::spawn(move || {
let mut value = 0;
let mut prev_value;
for _ in 0..ITERATIONS_NUMBER {
prev_value = value;
value = *reader.write_scratchpad().unwrap();
assert!(
prev_value <= value,
"Previous value = {}, value = {}",
prev_value,
value
);
assert_eq!(value, reader.epoch);
}
});
for _ in 0..ITERATIONS_NUMBER {
let mut guard = writer1.write().unwrap();
*guard = *guard + 1;
}
th_w.join().unwrap();
th_r.join().unwrap();
});
}
}