Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deadlock with AsyncCache when using wait and nested caches #30

Open
jrray opened this issue Oct 11, 2022 · 1 comment
Open

Deadlock with AsyncCache when using wait and nested caches #30

jrray opened this issue Oct 11, 2022 · 1 comment
Labels
bug Something isn't working enhancement New feature or request

Comments

@jrray
Copy link

jrray commented Oct 11, 2022

We have encountered a deadlock situation when attempting to have nested caches, as in a key-key-value multi-dimensional map, where there are many spawned tasks trying to interact with the cache at once, and the tasks attempt to call wait after insert of the inner value.

All the tokio (non-blocking) threads become blocked on trying to obtain a lock on the cache. The await inside wait allows one of the other tasks destined to block to be scheduled, eventually causing deadlock. The code below, inner_cache.wait() happens while a read lock on lru is held. Avoiding the inner_cache.wait() call prevents the deadlock.

We have restructured our code so it no longer uses nested caches and avoids this situation, but I wanted to share this discovery. This may not be a usage pattern that is intended to work or be supported. But to my eyes it is not immediately obvious that this would deadlock. I speculate that if the CacheProcessor was spawned as a dedicated thread instead of a green thread it might be able to complete the wait group and unstick things (we didn't experience a deadlock with this same design pattern when using the sync Cache).

Here is an example test that can demonstrate the deadlock:

    #[tokio::test(flavor = "multi_thread")]
    async fn test_wait_with_read_lock() {
        let max_cost = 10_000;
        let lru = Arc::new(
            AsyncCacheBuilder::new(max_cost * 10, max_cost as i64)
                .set_ignore_internal_cost(true)
                .finalize(tokio::spawn)
                .expect("failed to create cache"),
        );

        let key = 1;
        let cost = 1;

        let mut tasks = Vec::new();

        for i in 1..=10_000 {
            let lru = Arc::clone(&lru);
            tasks.push(tokio::spawn(async move {
                let inner_cache = match lru.get(&key) {
                    Some(v) => v,
                    None => {
                        let inner_lru = AsyncCacheBuilder::new(max_cost * 10, max_cost as i64)
                            .set_ignore_internal_cost(true)
                            .finalize(tokio::spawn)
                            .expect("failed to create cache");
                        lru.insert(key, inner_lru, cost).await;
                        lru.wait().await.unwrap();
                        lru.get(&key).unwrap()
                    }
                };
                let inner_cache = inner_cache.value();
                inner_cache.insert(i, 123, cost).await;
                eprintln!("i = {i}, len before wait = {}", inner_cache.len());
                // removing this wait avoids deadlock
                inner_cache.wait().await.unwrap();
            }));
        }

        for task in tasks {
            task.await.unwrap();
        }
    }
@al8n
Copy link
Owner

al8n commented Oct 11, 2022

Hi @jrray, I think this should be caused by the AsyncWaitGroup implementation of https://github.com/al8n/wg. I suspect we need to replace parking_lot::Mutex and parking_lot::RwLock with async locks for the AsyncCache.

@al8n al8n added bug Something isn't working enhancement New feature or request labels Jan 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants