Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Retries and timeouts

The timeout

Every configuration built with authenticated uses an HTTP client with a 30 second timeout, so a stalled connection cannot block a task forever. The timeout is a constant:

#![allow(unused)]
fn main() {
extern crate timeweb_rs;
use std::time::Duration;
use timeweb_rs::DEFAULT_TIMEOUT;

assert_eq!(DEFAULT_TIMEOUT, Duration::from_secs(30));
}

A request that runs into the timeout fails with Error::Reqwest.

Bring your own client when the default does not fit — a proxy, a connection pool or a different timeout:

extern crate reqwest;
extern crate timeweb_rs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
    .timeout(std::time::Duration::from_secs(5))
    .build()?;
let config = timeweb_rs::authenticated_with_client("your-jwt-token", client);
let _ = config;
Ok(())
}

The retry policy

TimewebClient retries an operation when the failure is worth retrying:

  • a transport failure — the request never reached the API;
  • 429 Too Many Requests;
  • any 5xx response.

Every other error is returned immediately. The delay grows exponentially from the base delay, doubles per attempt and is capped by the maximum delay; half of it is random, so the clients of a fleet do not retry in lockstep.

extern crate timeweb_rs;
fn main() {
use std::time::Duration;
use timeweb_rs::{RetryPolicy, TimewebClient};

let policy = RetryPolicy {
    max_retries: 5,
    base_delay:  Duration::from_millis(200),
    max_delay:   Duration::from_secs(10),
};

let client = TimewebClient::new("your-jwt-token").with_policy(policy);
assert_eq!(client.config().base_path, timeweb_rs::DEFAULT_BASE_URL);
}

The default policy retries three times, starting from 500 milliseconds and capping the delay at eight seconds.

Retry-After

A 429 may carry a Retry-After header, either as a number of seconds or as an HTTP date. The client honors it: the delay becomes exactly what the API asked for, capped by the maximum delay of the policy.

Idempotency

Only retry idempotent operations. A POST that creates a resource may reach the API even when the response never arrives, so retrying it can create the resource twice.

execute retries whatever you give it, because the policy is a property of the client, not of the operation. For an operation that is not idempotent, use execute_once:

extern crate tokio;
extern crate timeweb_rs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use timeweb_rs::{
    apis::ssh_api,
    models::CreateKeyRequest,
    TimewebClient,
};

let client = TimewebClient::new("your-jwt-token");
let key = client
    .execute_once(|| {
        ssh_api::create_key(
            client.config(),
            CreateKeyRequest {
                name: "deploy".to_owned(),
                body: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG…".to_owned(),
                ..CreateKeyRequest::default()
            },
        )
    })
    .await?;
println!("created key {}", key.ssh_key.id);
Ok(())
}

The policy is public

RetryPolicy::delay_for returns the nominal delay of a retry — the delay before the jitter is applied. It is deterministic, which makes it easy to assert on in tests.