Search code examples
multithreadingrustrust-tokioreqwesttauri

Rust request with timeout


When my app starts I need to make a reqwest request every 30 minutes, I tried the loop function with thread::sleep() but when the loop function starts, my main code stops.

This is my main.rs file

.setup(|app| {
            let config = app::storage::config::read(&app.config()).unwrap();

            if config.show_window_on_start {
                app::window::new(app.handle());
            }

            app::api::firebase::set_ip(server::network::get_ip(), &app.config());

            Ok(())
        })

firebase.rs with set_ip function

#[tokio::main]
pub async fn set_ip(ip: std::net::IpAddr, config: &tauri::Config) {
    let client = Client::new();

    let user = config::read(config).unwrap().user_data;

    let token = auth(user.email.as_str(), user.password.as_str()).await.unwrap();

    let response = client
        .get(
            format!(
                "https://firestore.googleapis.com/v1/my_db/(default)/documents/users/{}",
                user.email
            )
        )
        .header("Authorization", format!("Bearer {}", token))
        .header("content-type", "application/json")
        .send().await
        .unwrap()
        .text().await
        .unwrap();

    let mut data: serde_json::Value = serde_json::from_str(&response).expect("ERROR parsing");

    data["fields"]["ip"]["stringValue"] = serde_json::Value::String(ip.to_string());

    let _post_ip = client
        .patch(
            format!(
                "https://firestore.googleapis.com/v1/projects/my_db/documents/users/{}",
                user.email
            )
        )
        .body(serde_json::to_string(&data).expect("Error stringing value"))
        .header("Authorization", format!("Bearer {}", token))
        .header("content-type", "application/json")
        .send().await
        .unwrap();
}

Solution

  • Async runtimes assume there is no blocking code in the async context, and thread::sleep does nothing but block, which is why it breaks everything. You should use tokio::time::sleep if you need to sleep an async task.

    I'm not sure which part you want to timeout, especially since 30 minutes is an extraordinarily long time to wait for a network request, but you can build the Client with a default timeout.

    let client = Client::builder()
        .timeout(Duration::from_secs(60 * 30))
        .build()
        .unwrap();
    

    You can also construct each request with its own timeout.

    let req = client
        .get(url)
        .timeout(Duration::from_secs(60 * 30))
        .send()
        .await;
    

    When the request times out, it will end up in the Result::Err from one of the operations (either send or text in your code).

    let req = match req {
        Ok(ok) => ok,
        Err(err) => {
            if err.is_timeout() {
                // Do something with the timeout
                println!("encountered timeout");
                return;
            } else {
                panic!("{err}");
            }
        }
    };
    

    For more general usage of timing out any async task, you can use tokio's sleep and a select.

    let req = async {
        client
            .get(url)
            .send()
            .await
            .unwrap()
            .text()
            .await
            .unwrap()
    };
    
    let result = select! {
        result = req => result,
        _ = tokio::time::sleep(Duration::from_secs(60 * 30)) => {
            // Do something with the timeout
            println!("encountered timeout");
            return;
        }
    };
    

    There's no need to use this for the above situation since reqwest's builtin timeout will do the same thing, but if you wanted to timeout two requests, you could use this.

    If your code is all sequential (e.g. you aren't using task::spawn or select) like what you have in the question, then there's no need to use reqwest's async client. Enable the blocking feature and use the blocking client instead.

    # Cargo.toml
    reqwest = { version = "0.11.17", features = ["blocking"] }
    
    use reqwest::blocking::Client;
    
    let client = Client::builder()
        .timeout(Duration::from_secs(60 * 30))
        .build()
        .unwrap();
    
    let req = client.get(url).send().unwrap().text().unwrap();
    

    You can still use the timeout builder methods, just not tokio::time::sleep or select.