#![cfg(all(feature = "async", feature = "proc_macro"))]
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};
static CLONES: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, PartialEq)]
pub struct NotSyncVal {
n: u32,
_not_sync: PhantomData<std::cell::Cell<()>>,
}
impl NotSyncVal {
fn new(n: u32) -> Self {
NotSyncVal {
n,
_not_sync: PhantomData,
}
}
}
impl Clone for NotSyncVal {
fn clone(&self) -> Self {
CLONES.fetch_add(1, Ordering::SeqCst);
NotSyncVal::new(self.n)
}
}
const _: fn() = || {
fn assert_send<T: Send>() {}
assert_send::<NotSyncVal>();
};
fn to_string(v: &NotSyncVal) -> String {
v.n.to_string()
}
fn from_str(s: &str) -> NotSyncVal {
NotSyncVal::new(s.parse().expect("parse NotSyncVal"))
}
use cached::{ConcurrentCacheBase, ConcurrentCachedAsync, SerializeCachedAsync};
use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::Mutex;
pub struct NotSyncStore {
map: Mutex<HashMap<u32, String>>,
}
impl NotSyncStore {
pub fn new() -> Self {
NotSyncStore {
map: Mutex::new(HashMap::new()),
}
}
}
impl Default for NotSyncStore {
fn default() -> Self {
Self::new()
}
}
impl ConcurrentCacheBase for NotSyncStore {
type Error = Infallible;
}
impl ConcurrentCachedAsync<u32, NotSyncVal> for NotSyncStore {
fn async_cache_get(
&self,
k: &u32,
) -> impl std::future::Future<Output = Result<Option<NotSyncVal>, Infallible>> + Send {
let result = {
let map = self.map.lock().unwrap();
map.get(k).map(|s| from_str(s))
};
async move { Ok(result) }
}
fn async_cache_set(
&self,
k: u32,
v: NotSyncVal,
) -> impl std::future::Future<Output = Result<Option<NotSyncVal>, Infallible>> + Send {
let s = to_string(&v);
let prev = {
let mut map = self.map.lock().unwrap();
map.insert(k, s)
};
let prev_val = prev.map(|s| from_str(&s));
async move { Ok(prev_val) }
}
fn async_cache_remove(
&self,
k: &u32,
) -> impl std::future::Future<Output = Result<Option<NotSyncVal>, Infallible>> + Send {
let result = {
let mut map = self.map.lock().unwrap();
map.remove(k).map(|s| from_str(&s))
};
async move { Ok(result) }
}
fn async_cache_remove_entry(
&self,
k: &u32,
) -> impl std::future::Future<Output = Result<Option<(u32, NotSyncVal)>, Infallible>> + Send
{
let result = {
let mut map = self.map.lock().unwrap();
map.remove_entry(k).map(|(k, s)| (k, from_str(&s)))
};
async move { Ok(result) }
}
fn async_cache_clear(&self) -> impl std::future::Future<Output = Result<(), Infallible>> + Send
where
Self: Sync,
{
self.map.lock().unwrap().clear();
async move { Ok(()) }
}
fn async_cache_reset(&self) -> impl std::future::Future<Output = Result<(), Infallible>> + Send
where
Self: Sync,
{
self.map.lock().unwrap().clear();
async move { Ok(()) }
}
}
impl SerializeCachedAsync<u32, NotSyncVal> for NotSyncStore {
fn async_cache_set_ref(
&self,
k: &u32,
v: &NotSyncVal,
) -> impl std::future::Future<Output = Result<(), Infallible>> + Send {
let s = to_string(v);
let k = *k;
self.map.lock().unwrap().insert(k, s);
async move { Ok(()) }
}
}
use cached::macros::concurrent_cached;
#[concurrent_cached(
ty = "NotSyncStore",
create = "{ NotSyncStore::new() }",
map_error = "|e| e",
key = "u32",
convert = "{ n }"
)]
async fn via_not_sync(n: u32) -> Result<NotSyncVal, Infallible> {
Ok(NotSyncVal::new(n))
}
#[tokio::test]
#[serial_test::serial(notsync_clones)]
async fn not_sync_value_takes_owned_fallback_one_clone() {
CLONES.store(0, Ordering::SeqCst);
if let Some(store) = VIA_NOT_SYNC.get() {
store.async_cache_clear().await.unwrap();
}
let v = via_not_sync(7).await.unwrap();
assert_eq!(v, NotSyncVal::new(7));
assert_eq!(
CLONES.load(Ordering::SeqCst),
1,
"a !Sync value must take the owned fallback arm and clone exactly once at the set site"
);
CLONES.store(0, Ordering::SeqCst);
let hit = via_not_sync(7).await.unwrap();
assert_eq!(hit, NotSyncVal::new(7));
}