Tokio Mutex

To start, let's just swap out the Mutex and see what happens.

use serde::Serialize;
use serde_json::Value;
// We've dropped `sync::Mutex` here
use std::{
    collections::BTreeMap,
    sync::Arc,
    time::{Duration, Instant},
};
// This is our new import
use tokio::sync::Mutex;

async fn main() {
    let db = Arc::new(Mutex::new(Database::default()));
    tokio::task::spawn({
        let db = db.clone();
        async move {
            loop {
                let guard = db.lock().await;
                let value = guard.get("my-key");
                tokio::time::sleep(Duration::from_secs(1)).await;
                dbg!(value);
            }
        }
    });

    tokio::task::spawn(async move {
        let start = Instant::now();
        loop {
            db.lock()
                .await
                .insert("my-key", &start.elapsed().as_millis());
            tokio::time::sleep(Duration::from_millis(350)).await;
        }
    })
    .await
    .unwrap();
}

That worked! But what exactly is happening? Let's add some print statements to see if we can figure out the ordering of operations.

async fn main() {
    let db = Arc::new(Mutex::new(Database::default()));
    tokio::task::spawn({
        let db = db.clone();
        async move {
            loop {
                println!("->read-guard");
                let guard = db.lock().await;
                let value = guard.get("my-key");
                println!("->read-sleep");
                tokio::time::sleep(Duration::from_secs(1)).await;
                println!("<-read-sleep");
                dbg!(value);
                println!("<-read-guard")
            }
        }
    });

    tokio::task::spawn(async move {
        let start = Instant::now();
        loop {
            println!("->write-guard");
            db.lock()
                .await
                .insert("my-key", &start.elapsed().as_millis());
            println!("<-write-guard");
            tokio::time::sleep(Duration::from_millis(350)).await;
        }
    })
    .await
    .unwrap();
}

When we run this, we get the following output.

->read-guard
->read-sleep
->write-guard
<-read-sleep
[src/main.rs:55:17] value = None
<-read-guard
->read-guard
<-write-guard
->read-sleep
->write-guard
<-read-sleep
[src/main.rs:55:17] value = Some(
    Number(1002),
)
<-read-guard
->read-guard
<-write-guard

So, looking over our logs it seems like our get task locks our Mutex and then immeadly sleeps, tokio then selects our insert task which attempts to lock our Mutex however since our get task already has the lock it will yield back to tokio. Once our sleep finishes it prints out the value and drops the guard and then immedatly tries to lock the map again. At this point because the insert task called lock before our second lock in the get task, tokio will let our insert task start to make progress which inserts our first value.

Notice how the insert is delayed by the access in our get task, In this case that would be desirable since we don't want to be able to mutate a value when we don't have exclusive access to it.