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/output/broadcaster/**'
|
||||||
- 'asynchronix/src/ports/source/broadcaster.rs'
|
- 'asynchronix/src/ports/source/broadcaster.rs'
|
||||||
- 'asynchronix/src/ports/source/broadcaster/**'
|
- 'asynchronix/src/ports/source/broadcaster/**'
|
||||||
|
- 'asynchronix/src/util/cached_rw_lock.rs'
|
||||||
- 'asynchronix/src/util/slot.rs'
|
- 'asynchronix/src/util/slot.rs'
|
||||||
- 'asynchronix/src/util/sync_cell.rs'
|
- 'asynchronix/src/util/sync_cell.rs'
|
||||||
|
|
||||||
@ -31,4 +32,4 @@ jobs:
|
|||||||
- name: Run cargo test (Loom)
|
- name: Run cargo test (Loom)
|
||||||
run: cargo test --tests --release
|
run: cargo test --tests --release
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: --cfg asynchronix_loom
|
RUSTFLAGS: --cfg asynchronix_loom
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
#[cfg(asynchronix_loom)]
|
#[cfg(asynchronix_loom)]
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub(crate) mod sync {
|
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) mod atomic {
|
||||||
pub(crate) use loom::sync::atomic::{
|
pub(crate) use loom::sync::atomic::{
|
||||||
@ -12,7 +13,7 @@ pub(crate) mod sync {
|
|||||||
#[cfg(not(asynchronix_loom))]
|
#[cfg(not(asynchronix_loom))]
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub(crate) mod sync {
|
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) mod atomic {
|
||||||
pub(crate) use std::sync::atomic::{
|
pub(crate) use std::sync::atomic::{
|
||||||
|
@ -19,33 +19,55 @@
|
|||||||
//!
|
//!
|
||||||
//! #### Example
|
//! #### Example
|
||||||
//!
|
//!
|
||||||
//! The outputs in this example are clones of each other and remain therefore
|
//! This example demonstrates a submodel inside a parent model. The output of
|
||||||
//! always connected to the same inputs. For an example usage of outputs cloning
|
//! the submodel is a clone of the parent model output. Both outputs remain
|
||||||
//! in submodels assemblies, see the [`assembly example`][assembly].
|
//! therefore always connected to the same inputs.
|
||||||
|
//!
|
||||||
|
//! For a more comprehensive example demonstrating output cloning in submodels
|
||||||
|
//! assemblies, see the [`assembly example`][assembly].
|
||||||
//!
|
//!
|
||||||
//! [assembly]:
|
//! [assembly]:
|
||||||
//! https://github.com/asynchronics/asynchronix/tree/main/asynchronix/examples/assembly.rs
|
//! 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::ports::Output;
|
||||||
|
//! use asynchronix::simulation::Mailbox;
|
||||||
//!
|
//!
|
||||||
//! pub struct MyModel {
|
//! pub struct ChildModel {
|
||||||
//! pub output_a: Output<u64>,
|
//! pub output: Output<u64>,
|
||||||
//! pub output_b: Output<u64>,
|
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl MyModel {
|
//! impl ChildModel {
|
||||||
//! pub fn new() -> Self {
|
//! pub fn new() -> Self {
|
||||||
//! let output: Output<_> = Default::default();
|
|
||||||
//! Self {
|
//! Self {
|
||||||
//! output_a: output.clone(),
|
//! output: Default::default(),
|
||||||
//! output_b: output,
|
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! 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;
|
mod input;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
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.
|
/// A cached read-write lock.
|
||||||
///
|
///
|
||||||
@ -13,10 +14,9 @@ use std::sync::{Arc, LockResult, Mutex, MutexGuard, PoisonError};
|
|||||||
/// synchronization.
|
/// synchronization.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct CachedRwLock<T: Clone> {
|
pub(crate) struct CachedRwLock<T: Clone> {
|
||||||
local: T,
|
value: T,
|
||||||
local_epoch: usize,
|
epoch: usize,
|
||||||
shared: Arc<Mutex<T>>,
|
shared: Arc<Shared<T>>,
|
||||||
epoch: Arc<AtomicUsize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> CachedRwLock<T> {
|
impl<T: Clone> CachedRwLock<T> {
|
||||||
@ -24,61 +24,63 @@ impl<T: Clone> CachedRwLock<T> {
|
|||||||
pub(crate) fn new(t: T) -> Self {
|
pub(crate) fn new(t: T) -> Self {
|
||||||
let shared = t.clone();
|
let shared = t.clone();
|
||||||
Self {
|
Self {
|
||||||
local: t,
|
value: t,
|
||||||
local_epoch: 0,
|
epoch: 0,
|
||||||
shared: Arc::new(Mutex::new(shared)),
|
shared: Arc::new(Shared {
|
||||||
epoch: Arc::new(AtomicUsize::new(0)),
|
value: Mutex::new(shared),
|
||||||
|
epoch: AtomicUsize::new(0),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives access to the local cache without synchronization.
|
/// Gives access to the local cache without synchronization.
|
||||||
pub(crate) fn read_unsync(&self) -> &T {
|
pub(crate) fn read_unsync(&self) -> &T {
|
||||||
&self.local
|
&self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Synchronizes the local cache if it is behind the shared data and gives
|
/// Synchronizes the local cache if it is behind the shared data and gives
|
||||||
/// access to it.
|
/// access to it.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn read(&mut self) -> LockResult<&T> {
|
pub(crate) fn read(&mut self) -> LockResult<&T> {
|
||||||
if self.epoch.load(Ordering::Relaxed) != self.local_epoch {
|
if self.shared.epoch.load(Ordering::Relaxed) != self.epoch {
|
||||||
match self.shared.lock() {
|
match self.shared.value.lock() {
|
||||||
LockResult::Ok(shared) => {
|
LockResult::Ok(shared) => {
|
||||||
self.local = shared.clone();
|
self.value = shared.clone();
|
||||||
self.local_epoch = self.epoch.load(Ordering::Relaxed)
|
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
|
/// Gives write access to the local cache without synchronization so it can
|
||||||
/// be used as a scratchpad.
|
/// be used as a scratchpad.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn write_scratchpad_unsync(&mut self) -> &mut T {
|
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
|
/// 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.
|
/// write access to it so it can be used as a scratchpad.
|
||||||
pub(crate) fn write_scratchpad(&mut self) -> LockResult<&mut T> {
|
pub(crate) fn write_scratchpad(&mut self) -> LockResult<&mut T> {
|
||||||
if self.epoch.load(Ordering::Relaxed) != self.local_epoch {
|
if self.shared.epoch.load(Ordering::Relaxed) != self.epoch {
|
||||||
match self.shared.lock() {
|
match self.shared.value.lock() {
|
||||||
LockResult::Ok(shared) => {
|
LockResult::Ok(shared) => {
|
||||||
self.local = shared.clone();
|
self.value = shared.clone();
|
||||||
self.local_epoch = self.epoch.load(Ordering::Relaxed)
|
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.
|
/// Acquires a write lock on the shared data.
|
||||||
pub(crate) fn write(&mut self) -> LockResult<CachedRwLockWriteGuard<'_, T>> {
|
pub(crate) fn write(&mut self) -> LockResult<CachedRwLockWriteGuard<'_, T>> {
|
||||||
let guard = self.shared.lock();
|
let guard = self.shared.value.lock();
|
||||||
let epoch = self.epoch.load(Ordering::Relaxed) + 1;
|
let epoch = self.shared.epoch.load(Ordering::Relaxed) + 1;
|
||||||
self.epoch.store(epoch, Ordering::Relaxed);
|
self.shared.epoch.store(epoch, Ordering::Relaxed);
|
||||||
|
|
||||||
match guard {
|
match guard {
|
||||||
LockResult::Ok(shared) => LockResult::Ok(CachedRwLockWriteGuard { guard: shared }),
|
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.
|
/// Write guard.
|
||||||
///
|
///
|
||||||
/// The lock is released when the guard is dropped.
|
/// The lock is released when the guard is dropped.
|
||||||
@ -109,3 +116,59 @@ impl<T: Clone> DerefMut for CachedRwLockWriteGuard<'_, T> {
|
|||||||
&mut self.guard
|
&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