Search code examples
ruststaticrwlocklazy-static

Rust lazy_static variable RwLock access


I am trying to declare and read/write an instance of a custom struct, using lazy_static as I had to use non-const function at its initialization (string).

As I saw here in an other Stackoverflow post, I tried to use RwLock, which works fine when it comes to write, but fails when it comes to read, with the following error:

thread 'main' panicked at 'rwlock read lock would result in deadlock', /Users/adrien/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/std/src/sys/unix/rwlock.rs:47:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
pub struct Authentication {
    access_token: String,
    refresh_token: String,
    expiration: u32
}

lazy_static! {
    static ref LATEST_AUTH: RwLock<Authentication> = RwLock::new(Authentication {
        access_token: "access".to_string(),
        refresh_token: "refresh".to_string(),
        expiration: 0
    });
}

pub fn auth(){
   let api_resp: ApiResponse = res.json().unwrap(); //From a reqwest res
   let mut au = LATEST_AUTH.write().unwrap();
   au.access_token = api_resp.access_token.clone();
   println!("LATEST_AUTH:{}", LATEST_AUTH.read().unwrap()); //Fails
}

Solution

  • A RwLock is locked for the entire scope of the lock guard, which is obtained from calls to read() or write().

    In your case, the write lock guard, au, lives for the entire duration of the auth function. This is what the error is saying: you've already locked it and then trying to lock it again in the same thread will make it block forever.

    Write locks can also be used for reading, so you can fix this by just re-using that instead of trying to lock it again:

    pub fn auth(){
       let api_resp: ApiResponse = res.json().unwrap();
       let mut au = LATEST_AUTH.write().unwrap();
       au.access_token = api_resp.access_token.clone();
       println!("LATEST_AUTH:{}", au);
    }
    

    Alternatively, you can force the lock to be dropped sooner, so that you can lock it for reading separately:

    pub fn auth(){
       let api_resp: ApiResponse = res.json().unwrap();
       let mut au = LATEST_AUTH.write().unwrap();
       au.access_token = api_resp.access_token.clone();
       std::mem::drop(au);
       println!("LATEST_AUTH:{}", LATEST_AUTH.read().unwrap());
    }
    

    Or:

    pub fn auth(){
       let api_resp: ApiResponse = res.json().unwrap();
       // a new scope
       {
           let mut au = LATEST_AUTH.write().unwrap();
           au.access_token = api_resp.access_token.clone();
           // au will be dropped here, when it goes out of scope
       }
       println!("LATEST_AUTH:{}", LATEST_AUTH.read().unwrap());
    }