Repository: jaemk/cached (language: rust)

Issue:
Once with sync_writes and async causes deadlocks in tokio.
the following code will freeze:
```
use cached::proc_macro::once;
use std::time::Duration;
use tokio::time::sleep;

#[once(time = 1, sync_writes = true)]
async fn once_writes_per_second() -> String {
    "results".to_string()
}

async fn sleep_secs(secs: u64) {
    sleep(Duration::from_secs(secs)).await;
}

#[tokio::main]
async fn main() {
    let _ = once_writes_per_second().await;
    println!("sleeping for 2 seconds");
    sleep_secs(2).await;
    let _ = once_writes_per_second().await; // will freeze
    println!("executed");
}
```

it freezes because it will expand to (I redacted the unimportant parts with ...):
```
async fn once_writes_per_second() -> String {
    ...
    let mut cached = ONCE_WRITES_PER_SECOND.write().await;
    if let Some(result) = &*cached {
        {
            let mut cached = ONCE_WRITES_PER_SECOND.read().await;
           ...
        }
    }
   ...
    *cached = Some((now, result.clone()));
    result
}

```

and ONCE_WRITES_PER_SECOND is tokio::sync::RwLock which is not re-entry, and will not allow for read lock to be obtained while keeping write lock. Besides, if we have write lock there is no need for read lock anyway.


Discussion / hints:

Fix patch:
diff --git a/cached_proc_macro/src/once.rs b/cached_proc_macro/src/once.rs
index a699623..cbcd0d0 100644
--- a/cached_proc_macro/src/once.rs
+++ b/cached_proc_macro/src/once.rs
@@ -205,7 +205,7 @@ pub fn once(args: TokenStream, input: TokenStream) -> TokenStream {
         #set_cache_and_return
     };
 
-    let return_cache_block = quote! {
+    let r_lock_return_cache_block = quote! {
         {
             #r_lock
             if let Some(result) = &*cached {
@@ -216,7 +216,7 @@ pub fn once(args: TokenStream, input: TokenStream) -> TokenStream {
 
     let do_set_return_block = if args.sync_writes {
         quote! {
-            #return_cache_block
+            #r_lock_return_cache_block
             #w_lock
             if let Some(result) = &*cached {
                 #return_cache_block
@@ -226,7 +226,7 @@ pub fn once(args: TokenStream, input: TokenStream) -> TokenStream {
         }
     } else {
         quote! {
-            #return_cache_block
+            #r_lock_return_cache_block
             #function_call
             #w_lock
             #set_cache_and_return


Test patch:
diff --git a/tests/cached.rs b/tests/cached.rs
index 77001db..43fbc29 100644
--- a/tests/cached.rs
+++ b/tests/cached.rs
@@ -898,6 +898,33 @@ async fn test_cached_sync_writes_a() {
     assert_eq!(a, c.await.unwrap());
 }
 
+#[cfg(feature = "async")]
+#[once(sync_writes = true)]
+async fn once_sync_writes_a(s: &tokio::sync::Mutex<String>) -> String {
+    let mut guard = s.lock().await;
+    let results: String = (*guard).clone().to_string();
+    *guard = "consumed".to_string();
+    results.to_string()
+}
+
+#[cfg(feature = "async")]
+#[tokio::test]
+async fn test_once_sync_writes_a() {
+    let a_mutex = tokio::sync::Mutex::new("a".to_string());
+    let b_mutex = tokio::sync::Mutex::new("b".to_string());
+    let fut_a = once_sync_writes_a(&a_mutex);
+    let fut_b = once_sync_writes_a(&b_mutex);
+    let a = fut_a.await;
+    let b = fut_b.await;
+    assert_eq!(a, b);
+    assert_eq!("a", a);
+
+    //check if cache function is executed for a
+    assert_eq!("consumed", a_mutex.lock().await.to_string());
+    //check if cache inner is not executed for b (not executed second time)
+    assert_eq!("b", b_mutex.lock().await.to_string());
+}
+
 #[cached(size = 2)]
 fn cached_smartstring(s: smartstring::alias::String) -> smartstring::alias::String {
     if s == "very stringy" {