Tokio RwLock max reads

I'm hitting a roadblock with Tokio's RwLock due to how it sets the semaphore permit to 32 by default.

Here is an example. Rust Playground

use std::sync::Arc;
use tokio::spawn;
use tokio::sync::RwLock;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let lock = Arc::new(RwLock::new(0));
    for _ in 0..60 {
        spawn({
            let lock = lock.clone();
            async move {
                let guard = lock.read().await;
                dbg!(&lock);
                sleep(Duration::from_secs(60 * 60 * 24)).await;
            }
        });
    }

    sleep(Duration::from_millis(500)).await;
    dbg!(&lock);
    lock.read().await; // Blocks for a long time here
}

I'm only reading the RwLock, so all reads should not block when I try to lock it. Granted, the docs for RwLock says:

This type of lock allows a number of readers

I just need it to set the permit number to 128 or higher for my project. I'm making a Scratch virtual machine (Scratch the PL) and for big Scratch files they can have more than 32 concurrently executing sprites. I have them all indexed on a RwLock<HashMap<SpriteID, Sprite>>. When too many sprites are executing, other sprites or the redraw function start blocking for a long time, even if none of them want write access.

Is there an alternative async reader-writer lock that can be set to initialize with 128 permits or more?

Ah, the answer immediately came to me just after posting. async_std's RwLock is the answer. It looks like they use a bit flag to indicate reading/writing status, not permits.

use async_lock::RwLock;
use std::sync::Arc;
use tokio::spawn;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let lock = Arc::new(RwLock::new(0));
    for _ in 0..60 {
        spawn({
            let lock = lock.clone();
            async move {
                let guard = lock.read().await;
                sleep(Duration::from_secs(60 * 60 * 24)).await;
            }
        });
    }

    sleep(Duration::from_millis(500)).await;
    lock.read().await; // Does not block
}

Edited to use async-lock.

1 Like

That code is also broken out as a standalone crate async_lock if you don’t want to pull in all of async_std

2 Likes

Perfect. My compile times are too long already. I'll take all the feature flags and standalone libraries I can take.

It's definitely preferable to add an external library like async_lock over adding async_std. You generally should avoid mixing Tokio with async_std.

Also, I encourage you to open an issue on the Tokio issue tracker about this.

Good idea. I will.

GitHub issue created: RwLock blocks read access when there are many concurrent reads · Issue #3633 · tokio-rs/tokio · GitHub