Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions integration-tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions miner-apps/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pool-apps/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions stratum-apps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum"], optional = true }
# Common external dependencies that roles always need
ext-config = { version = "0.14.0", features = ["toml"], package = "config" }
shellexpand = "3.1.1"
dashmap = "6.1.0"

[features]
default = ["network", "config", "std"]
Expand Down
3 changes: 3 additions & 0 deletions stratum-apps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,6 @@ pub mod coinbase_output_constraints;

/// Fallback coordinator
pub mod fallback_coordinator;

/// Share synchronous primitives
pub mod shared;
258 changes: 258 additions & 0 deletions stratum-apps/src/shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
use std::{
hash::Hash,
sync::{Arc, RwLock},
};

use dashmap::DashMap;
use std::sync::Mutex;

/// Thread-safe shared mutable value using `Mutex` for exclusive access.
#[derive(Debug)]
pub struct Shared<T>(Arc<Mutex<T>>);

impl<T> Clone for Shared<T> {
fn clone(&self) -> Self {
Shared(Arc::clone(&self.0))
}
}

impl<T> Shared<T> {
/// Create a new shared value.
pub fn new(v: T) -> Self {
Shared(Arc::new(Mutex::new(v)))
}

/// Execute a closure with mutable access to the inner value.
pub fn with<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut T) -> R,
{
let mut lock = self.0.lock().unwrap();
f(&mut *lock)
}

/// Get a cloned snapshot of the value.
pub fn get(&self) -> T
where
T: Clone,
{
self.with(|v| v.clone())
}

/// Replace the inner value.
pub fn set(&self, value: T) {
self.with(|v| *v = value);
}
}

/// Thread-safe shared value using `RwLock` for concurrent reads and exclusive writes.
#[derive(Debug)]
pub struct SharedRw<T>(Arc<RwLock<T>>);

impl<T> Clone for SharedRw<T> {
fn clone(&self) -> Self {
SharedRw(Arc::clone(&self.0))
}
}

impl<T> SharedRw<T> {
/// Create a new shared value.
pub fn new(v: T) -> Self {
SharedRw(Arc::new(RwLock::new(v)))
}

/// Execute a closure with read-only access.
pub fn read<F, R>(&self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
let guard = self.0.read().unwrap();
f(&*guard)
}

/// Execute a closure with mutable access.
pub fn write<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut T) -> R,
{
let mut guard = self.0.write().unwrap();
f(&mut *guard)
}

/// Get a cloned snapshot of the value.
pub fn get(&self) -> T
where
T: Clone,
{
self.read(|v| v.clone())
}

/// Replace the inner value.
pub fn set(&self, value: T) {
self.write(|v| *v = value);
}
}

/// Concurrent map wrapper over `DashMap` providing ergonomic scoped access.
pub struct SharedMap<K: Eq + Clone + Hash, V>(Arc<DashMap<K, V>>);

impl<K: Eq + Clone + Hash, V> Clone for SharedMap<K, V> {
fn clone(&self) -> Self {
SharedMap(Arc::clone(&self.0))
}
}

impl<K: Eq + Hash + Clone, V> SharedMap<K, V> {
/// Create a new concurrent map.
pub fn new() -> Self {
SharedMap(Arc::new(DashMap::new()))
}

/// Read a value for a key using a closure.
pub fn with<F, R>(&self, key: &K, f: F) -> Option<R>
where
F: FnOnce(&V) -> R,
{
let guard = self.0.get(key)?;
let result = f(guard.value());
drop(guard);
Some(result)
}

/// Mutate a value for a key using a closure.
pub fn with_mut<F, R>(&self, key: &K, f: F) -> Option<R>
where
F: FnOnce(&mut V) -> R,
{
let mut guard = self.0.get_mut(key)?;
let result = f(guard.value_mut());
Some(result)
}

/// Iterate over all entries immutably.
pub fn for_each<F, Ret>(&self, mut f: F)
where
F: FnMut(K, &V) -> Ret,
{
for entry in self.0.iter() {
f(entry.key().clone(), entry.value());
}
}

/// Iterate over all entries mutably.
pub fn for_each_mut<F, Ret>(&self, mut f: F)
where
F: FnMut(K, &mut V) -> Ret,
{
for mut entry in self.0.iter_mut() {
f(entry.key().clone(), entry.value_mut());
}
}

/// Fallible mutable iteration over all entries.
pub fn try_for_each_mut<F, E>(&self, mut f: F) -> Result<(), E>
where
F: FnMut(K, &mut V) -> Result<(), E>,
{
for mut entry in self.0.iter_mut() {
f(entry.key().clone(), entry.value_mut())?;
}
Ok(())
}

/// Insert a key-value pair.
pub fn insert(&self, key: K, value: V) -> Option<V> {
self.0.insert(key, value)
}

/// Remove a key.
pub fn remove(&self, key: &K) -> Option<(K, V)> {
self.0.remove(key)
}

/// Check if a key exists.
pub fn contains_key(&self, key: &K) -> bool {
self.0.contains_key(key)
}

/// Retain entries matching predicate.
pub fn retain<F>(&self, f: F)
where
F: FnMut(&K, &mut V) -> bool,
{
self.0.retain(f);
}

/// Collect all keys.
pub fn keys(&self) -> Vec<K>
where
K: Clone,
{
self.0.iter().map(|e| e.key().clone()).collect()
}

/// Number of entries.
pub fn len(&self) -> usize {
self.0.len()
}

/// Check if empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}

impl<K: Eq + Hash + Clone, V> Default for SharedMap<K, V> {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn shared_basic_usage() {
let v = Shared::new(10);

v.with(|x| *x += 5);
assert_eq!(v.get(), 15);

v.set(42);
assert_eq!(v.get(), 42);
}

#[test]
fn shared_rw_usage() {
let v = SharedRw::new(100);

let a = v.read(|x| *x);
let b = v.read(|x| *x);
assert_eq!(a, b);

v.write(|x| *x += 1);
assert_eq!(v.get(), 101);
}

#[test]
fn shared_map_usage() {
let map = SharedMap::new();

map.insert("a", 1);
map.insert("b", 2);

let val = map.with(&"a", |v| *v).unwrap();
assert_eq!(val, 1);

map.with_mut(&"a", |v| *v += 10);
assert_eq!(map.with(&"a", |v| *v).unwrap(), 11);

let mut sum = 0;
map.for_each(|_, v| sum += v);
assert_eq!(sum, 13);

map.remove(&"a");
assert!(!map.contains_key(&"a"));
}
}
Loading