pub mod generative;
use crate::__internal_prelude::*;
use core::ops::Deref;
use std::sync::PoisonError;
#[delegatable_trait]
pub trait ThreadSafeCacheStore<'lock>
where
Self: 'lock,
{
type Key;
type Value;
type SLock<'guard>: From<&'guard Self::XLock>
where
'lock: 'guard;
type XLock: 'lock;
fn ts_get(&'lock self, handle: &Self::SLock<'_>) -> Option<Self::Value>;
fn ts_set(&'lock self, handle: &mut Self::XLock, value: &Self::Value);
fn ts_exists(&'lock self, handle: &Self::SLock<'_>) -> bool {
self.ts_get(handle).is_some()
}
fn ts_one_get(&'lock self, key: &Self::Key) -> Option<Self::Value> {
let handle = self.ts_slock(key);
self.ts_get(&handle)
}
fn ts_one_set(&'lock self, key: &Self::Key, value: &Self::Value) {
let mut handle = self.ts_xlock(key);
self.ts_set(&mut handle, value);
}
fn ts_one_exists(&'lock self, key: &Self::Key) -> bool {
let handle = self.ts_slock(key);
self.ts_exists(&handle)
}
fn ts_xlock(&'lock self, key: &Self::Key) -> Self::XLock;
fn ts_slock(&'lock self, key: &Self::Key) -> Self::SLock<'lock>;
fn ts_xlock_nblock(&'lock self, key: &Self::Key) -> Self::XLock;
fn ts_slock_nblock(&'lock self, key: &Self::Key) -> Self::SLock<'lock>;
}
#[delegatable_trait]
#[allow(clippy::missing_errors_doc)]
pub trait ThreadSafeTryCacheStore<'lock>
where
Self: 'lock,
{
type Key;
type Value;
type SLock<'guard>: From<&'guard Self::XLock>
where
'lock: 'guard;
type XLock: 'lock;
type Error;
fn ts_try_get(
&'lock self,
handle: &Self::SLock<'_>,
) -> Result<Option<Self::Value>, Self::Error>;
fn ts_try_set(
&'lock self,
handle: &mut Self::XLock,
value: &Self::Value,
) -> Result<(), Self::Error>;
fn ts_try_exists(&'lock self, handle: &Self::SLock<'_>) -> Result<bool, Self::Error> {
self.ts_try_get(handle).map(|v| v.is_some())
}
fn ts_one_try_get(
&'lock self,
key: &'lock Self::Key,
) -> Result<Option<Self::Value>, Self::Error> {
let handle = self.ts_try_slock(key)?;
self.ts_try_get(&handle)
}
fn ts_one_try_set(
&'lock self,
key: &'lock Self::Key,
value: &Self::Value,
) -> Result<(), Self::Error> {
let mut handle = self.ts_try_xlock(key)?;
self.ts_try_set(&mut handle, value)
}
fn ts_one_try_exists(&'lock self, key: &'lock Self::Key) -> Result<bool, Self::Error> {
let handle = self.ts_try_slock(key)?;
self.ts_try_exists(&handle)
}
fn ts_try_xlock(&'lock self, key: &'lock Self::Key) -> Result<Self::XLock, Self::Error>;
fn ts_try_slock(&'lock self, key: &'lock Self::Key) -> Result<Self::SLock<'lock>, Self::Error>;
fn ts_try_xlock_nblock(&'lock self, key: &'lock Self::Key) -> Result<Self::XLock, Self::Error>;
fn ts_try_slock_nblock(
&'lock self,
key: &'lock Self::Key,
) -> Result<Self::SLock<'lock>, Self::Error>;
}
impl<
'lock,
K,
V,
SL: for<'b> From<&'b XL> + 'lock,
XL: 'lock,
T: ThreadSafeCacheStore<'lock, Key = K, Value = V, SLock<'lock> = SL, XLock = XL>,
> ThreadSafeTryCacheStore<'lock> for T
{
type Key = K;
type Value = V;
type SLock<'guard>
= SL
where
'lock: 'guard;
type XLock = XL;
type Error = ();
fn ts_try_get(
&'lock self,
handle: &Self::SLock<'lock>,
) -> Result<Option<Self::Value>, Self::Error> {
Ok(self.ts_get(handle))
}
fn ts_try_set(
&'lock self,
handle: &mut Self::XLock,
value: &Self::Value,
) -> Result<(), Self::Error> {
#[allow(clippy::unit_arg)]
Ok(self.ts_set(handle, value))
}
fn ts_try_exists(&'lock self, handle: &Self::SLock<'lock>) -> Result<bool, Self::Error> {
Ok(self.ts_exists(handle))
}
fn ts_try_slock(&'lock self, key: &'lock Self::Key) -> Result<Self::SLock<'lock>, Self::Error> {
Ok(self.ts_slock(key))
}
fn ts_try_xlock(&'lock self, key: &'lock Self::Key) -> Result<Self::XLock, Self::Error> {
Ok(self.ts_xlock(key))
}
fn ts_try_slock_nblock(
&'lock self,
key: &'lock Self::Key,
) -> Result<Self::SLock<'lock>, Self::Error> {
Ok(self.ts_slock_nblock(key))
}
fn ts_try_xlock_nblock(&'lock self, key: &'lock Self::Key) -> Result<Self::XLock, Self::Error> {
Ok(self.ts_xlock_nblock(key))
}
}
#[macro_export]
macro_rules! implThreadUnsafe {
($for:expr) => {
impl<K, V> CacheStore for $for {
type Key = K;
type Value = V;
fn get(&self, key: &Self::Key) -> Option<Self::Value> {
self.ts_one_get(key)
}
fn set(&mut self, key: &Self::Key, value: &Self::Value) {
self.ts_one_set(key, value)
}
fn exists(&self, key: &Self::Key) -> bool {
self.ts_one_exists(key)
}
}
};
}
pub use implThreadUnsafe;
#[macro_export]
macro_rules! implTryThreadUnsafe {
($for:ty, $( $t:tt $( : $tb:ident)? ),*) => {
impl<$($t $( : $tb)?),*> TryCacheStore for $for
{
type Key = K;
type Value = V;
type Error = E;
fn try_get(&self, key: &Self::Key) -> Result<Option<Self::Value>, Self::Error> {
self.ts_one_try_get(key)
}
fn try_set(&mut self, key: &Self::Key, value: &Self::Value) -> Result<(), Self::Error> {
self.ts_one_try_set(key, value)
}
fn try_exists(&self, key: &Self::Key) -> Result<bool, Self::Error> {
self.ts_one_try_exists(key)
}
}
};
}
pub use implTryThreadUnsafe;
pub mod dumb_wrappers {
use core::{convert::Infallible, marker::PhantomData};
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError};
#[allow(clippy::wildcard_imports)]
use super::*;
#[derive(Debug)]
pub enum EmptyDumbError {
Poisoned,
WouldBlock,
}
impl std::error::Error for EmptyDumbError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl std::fmt::Display for EmptyDumbError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Poisoned => writeln!(f, "poisoned lock"),
Self::WouldBlock => writeln!(f, "locking would block"),
}
}
}
impl From<Infallible> for EmptyDumbError {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl<T> From<PoisonError<T>> for EmptyDumbError {
fn from(_: PoisonError<T>) -> Self {
Self::Poisoned
}
}
impl<T> From<TryLockError<T>> for EmptyDumbError {
fn from(value: TryLockError<T>) -> Self {
match value {
TryLockError::Poisoned(_) => Self::Poisoned,
TryLockError::WouldBlock => Self::WouldBlock,
}
}
}
pub struct DumbTryThreadSafeWrapper<
'a,
K,
V,
E,
S: TryCacheStore<Key = K, Value = V, Error = E>,
> {
pub store: RwLock<S>,
__phantom: PhantomData<&'a ()>,
}
impl<K, V, E, S: TryCacheStore<Key = K, Value = V, Error = E>>
DumbTryThreadSafeWrapper<'_, K, V, E, S>
{
pub fn new(store: S) -> Self {
Self {
store: RwLock::new(store),
__phantom: PhantomData,
}
}
}
pub enum RwLockAnyGuardKey<'lock, 'guard, T, K> {
Read((RwLockReadGuard<'lock, T>, &'lock K)),
Write(&'guard (RwLockWriteGuard<'lock, T>, &'lock K)),
}
impl<'lock, T, K> RwLockAnyGuardKey<'lock, '_, T, K> {
#[must_use]
pub fn get_key(&self) -> &'lock K {
match self {
Self::Read((_, k)) | Self::Write((_, k)) => k,
}
}
}
impl<'lock, T, K> From<(RwLockReadGuard<'lock, T>, &'lock K)>
for RwLockAnyGuardKey<'lock, '_, T, K>
{
fn from(value: (RwLockReadGuard<'lock, T>, &'lock K)) -> Self {
Self::Read(value)
}
}
impl<'lock, 'guard, T, K> From<&'guard (RwLockWriteGuard<'lock, T>, &'lock K)>
for RwLockAnyGuardKey<'lock, 'guard, T, K>
{
fn from(value: &'guard (RwLockWriteGuard<'lock, T>, &'lock K)) -> Self {
Self::Write(value)
}
}
impl<T, K> Deref for RwLockAnyGuardKey<'_, '_, T, K> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Self::Read((l, _)) => l,
Self::Write((l, _)) => l,
}
}
}
impl<'lock, K, V, E, S> ThreadSafeTryCacheStore<'lock>
for DumbTryThreadSafeWrapper<'lock, K, V, E, S>
where
Self: 'lock,
S: TryCacheStore<Key = K, Value = V, Error = E> + 'lock,
E: From<PoisonError<RwLockReadGuard<'lock, S>>>
+ From<PoisonError<RwLockWriteGuard<'lock, S>>>
+ From<TryLockError<RwLockReadGuard<'lock, S>>>
+ From<TryLockError<RwLockWriteGuard<'lock, S>>>,
{
type Key = K;
type Value = V;
type SLock<'guard>
= RwLockAnyGuardKey<'lock, 'guard, S, Self::Key>
where
'lock: 'guard;
type XLock = (RwLockWriteGuard<'lock, S>, &'lock Self::Key);
type Error = E;
fn ts_try_get(&self, handle: &Self::SLock<'_>) -> Result<Option<Self::Value>, Self::Error> {
handle.try_get(handle.get_key())
}
fn ts_try_set(
&self,
handle: &mut Self::XLock,
value: &Self::Value,
) -> Result<(), Self::Error> {
handle.0.try_set(handle.1, value)
}
fn ts_try_exists(&self, handle: &Self::SLock<'_>) -> Result<bool, Self::Error> {
handle.try_exists(handle.get_key())
}
fn ts_try_slock(
&'lock self,
key: &'lock Self::Key,
) -> Result<Self::SLock<'lock>, Self::Error> {
Ok((self.store.read()?, key).into())
}
fn ts_try_xlock(&'lock self, key: &'lock Self::Key) -> Result<Self::XLock, Self::Error> {
Ok((self.store.write()?, key))
}
fn ts_try_slock_nblock(
&'lock self,
key: &'lock Self::Key,
) -> Result<Self::SLock<'lock>, Self::Error> {
Ok((self.store.try_read()?, key).into())
}
fn ts_try_xlock_nblock(
&'lock self,
key: &'lock Self::Key,
) -> Result<Self::XLock, Self::Error> {
Ok((self.store.try_write()?, key))
}
}
}
#[cfg(test)]
#[cfg(feature = "std")]
mod tests {
use std::sync::Arc;
use crate::prelude::*;
use crate::stores::MemoryStore;
use crate::TryCacheStoreErrorMap;
use super::dumb_wrappers::{DumbTryThreadSafeWrapper, EmptyDumbError};
use rayon::iter::{ParallelBridge, ParallelIterator};
#[test]
fn write_1k_threads_same_key() {
let fstore: TryCacheStoreErrorMap<_, _, _, EmptyDumbError, _> =
MemoryStore::default().into();
let store: DumbTryThreadSafeWrapper<(), usize, EmptyDumbError, _> =
DumbTryThreadSafeWrapper::new(fstore);
let store = Arc::new(store);
let store_clone = Arc::clone(&store);
let n = 1000;
(0..n)
.par_bridge()
.try_for_each(move |_| {
let store = &store_clone;
let mut handle = store.ts_try_xlock(&()).ok()?;
let value = store.ts_try_get(&(&handle).into()).ok()?.unwrap_or(0);
store.ts_try_set(&mut handle, &(value + 1)).ok()?;
drop(handle);
Some(())
})
.expect("some thread failed");
assert_eq!(store.ts_one_try_get(&()).unwrap(), Some(n));
}
}