forked from ROMEO/nexosim
Add test and improve example
This commit is contained in:
parent
287b3b713c
commit
02eec1b277
3
.github/workflows/loom.yml
vendored
3
.github/workflows/loom.yml
vendored
@ -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'
|
||||
|
||||
@ -31,4 +32,4 @@ jobs:
|
||||
- name: Run cargo test (Loom)
|
||||
run: cargo test --tests --release
|
||||
env:
|
||||
RUSTFLAGS: --cfg asynchronix_loom
|
||||
RUSTFLAGS: --cfg asynchronix_loom
|
||||
|
@ -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::{
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user