0

I am trying to get the OAuth token from the server, so I made a struct named Token. In order to keep only one request when the token is expired, I keep a reference for the requesting future, let other requests keep waiting for it returns. Playground:

use futures::lock::Mutex; // 0.3.18
use futures_util::{future::Shared, Future, FutureExt}; // 0.3.18
use std::{pin::Pin, sync::Arc};
use tokio::time::Duration; // 1.14.0

pub struct Token {
    pub token: String,
    pub requesting:
        Arc<Mutex<Option<Shared<Pin<Box<dyn Future<Output = Result<String, ()>> + Send>>>>>>,
}

impl Token {
    // Get token from server, I hope it does not run repeatly
    async fn get_access_token_from_server(&mut self) -> Result<String, ()> {
        tokio::time::sleep(Duration::from_secs(10)).await;
        self.token = "test".to_owned();
        Ok(self.token.clone())
    }

    //Shows error:`is required to live as long as `'static` here`, why?
    pub async fn access_token(&mut self) -> Result<String, ()> {
        /*
        if !self.token.is_expire() {
            return Ok(self.token.clone());
        }
        */

        let req_arc = self.requesting.clone();
        let req_mutex = req_arc.lock().await;

        let req = if req_mutex.is_some() {
            req_mutex.clone().map(|s| s.clone()).unwrap()
        } else {
            let req = self.get_access_token_from_server().boxed().shared();
            *req_mutex = Some(req.clone());
            req
        };

        req.await
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /*
        Just example, in the real code, the token will keep in another struct with long life
    */
    let mut token = Token {
        token: String::from(""),
        requesting: Arc::new(Mutex::new(None)),
    };
    let token_string = token.access_token().await;
    println!("{:?}", token_string);
    Ok(())
}

I am confused about the lifetime errors:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/main.rs:21:31
   |
21 |     pub async fn access_token(&mut self) -> Result<String, ()> {
   |                               ^^^^^^^^^
   |                               |
   |                               this data with an anonymous lifetime `'_`...
   |                               ...is captured here...
...
35 |             *req_mutex = Some(req.clone());
   |                               ----------- ...and is required to live as long as `'static` here

I do not want to keep is as static; I hope its lifetime is the same as the struct.

  1. Is the normal way to solve the problem?
  2. How to correct the code?
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Henry
  • 531
  • 3
  • 12
  • The title needs to be edited to accurately reflect the issue. Also, a simplified code would be more helpful for others to understand and triage the issue. – Joe_Jingyu Dec 07 '21 at 12:21
  • @Joe_Jingyu I changed the title, the code is already simplified as much as I can. I added some comments to help understand. thanks for your suggestion. – Henry Dec 07 '21 at 12:36

1 Answers1

2

Unfortunately, the compiler doesn't make it obvious where the constraint comes from. Storing a dyn Future<...> without any lifetime annotation defaults to 'static and you can't really change it to the lifetime of self because self-referential structs are problematic.

Looking at your goal, you can use a thread-safe and async-capable OnceCell (from tokio for example) and skip all this complexity.

use tokio::time::Duration;
use tokio::sync::OnceCell;

pub struct Token {
    pub token: OnceCell<String>,
}

impl Token {
    pub async fn access_token(&self) -> Result<&String, ()> {
        self.token.get_or_try_init(|| async {
            // simulate auth call
            tokio::time::sleep(Duration::from_secs(1)).await;
            Ok("test".to_owned())
        }).await
    }
}

See it working on the playground.

I think it should behave as you expect. Multiple threads can await it but only one async task will execute and persist the value, and then any other awaits will simply fetch that value. And if the task returns an error, the next await will retry.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Thank you for your answer. In this case, the token may expire, so is there a better way to check (may update) and return using OnceCell? – Henry Dec 08 '21 at 02:03
  • In that case, you may still want it behind a `RwLock` so that you can still access it mutably and [`take()`](https://docs.rs/tokio/1.14.0/tokio/sync/struct.OnceCell.html#method.take) the value out of the cell, allowing it to reacquire the token. – kmdreko Dec 08 '21 at 03:20