Retries
Retry policies for both clients — the HTTP client's built-in HttpRetryPolicy for idempotent verbs, the wire client's with_retry combinator over RetryPolicy, and how deadlines cancel a call.
HTTP client
BrainHttpClient retries only the idempotent verbs — encode (stable
server-side request id), whoami, and capabilities — and only when the
failure is a transport error (status: 0) or an HTTP 503. Every other status,
including all 4xx, is terminal. Backoff grows exponentially from the base
delay, capped at the max; a server Retry-After header (integer seconds)
overrides the schedule, still capped at the max.
max_attemptsu32optionalTotal attempts including the first. Default 3. 1 disables retry.
base_delayDurationoptionalFirst backoff; doubles each attempt. Default 100 ms.
max_delayDurationoptionalCap on any single backoff (and on a server Retry-After). Default 2 s.
Configure it with the constructor builder:
use std::time::Duration;
use brain_db_sdk::{BrainHttpClient, HttpRetryPolicy};
// Default is HttpRetryPolicy::new(3, 100ms, 2s).
let client = BrainHttpClient::new("https://api.arc-labs.ai", "my-api-key")
.with_retry_policy(HttpRetryPolicy::new(
5, // max attempts
Duration::from_millis(200), // base backoff
Duration::from_secs(4), // cap
));
// Opt out entirely:
let once = BrainHttpClient::localhost("my-api-key")
.with_retry_policy(HttpRetryPolicy::none());recall, forget, link, unlink, plan, and reason are not retried
by the HTTP client — they make a single attempt. Retry them yourself only when
you know the operation is safe to repeat.
Wire client — with_retry
The wire client does not bake retry into each verb. Wrap a call in with_retry
and it re-runs while the error is retryable
(is_retryable) and attempts remain. Because
each builder mints a stable request_id, re-sending the same request is
idempotent server-side (24-hour idempotency window), so a retried verb does not
double-apply.
use brain_db_sdk::{with_retry, RetryPolicy};
// RetryPolicy::default() = 3 attempts, 100ms base backoff, 5s cap.
let policy = RetryPolicy::new(4, std::time::Duration::from_millis(100), std::time::Duration::from_secs(5));
// Build the request once so its request_id stays stable across attempts.
let req = EncodeBuilder::new("part of a retried write").build();
let resp = with_retry(&policy, || client.encode(&req)).await?;with_default_retry(|| ...) is shorthand for RetryPolicy::default(). Build a
custom schedule with RetryPolicy::new(max_attempts, base_delay, max_delay), or
RetryPolicy::none() for a single attempt.
Pass a closure that rebuilds the future (|| client.encode(&req)), not a
single future — the combinator re-invokes it from scratch on each attempt. Reuse
the same req so the request_id stays stable; rebuilding the request each
try mints a fresh id and defeats server-side idempotency.
The backoff honors a server-supplied retry_after (surfaced by
err.retry_after()) when present, otherwise grows exponentially from the base
delay (capped at the max), then applies full jitter so a herd of clients that
failed together don't retry in lockstep.
A transport drop (BrainError::Closed, an Io error) is reported retryable,
but a single BrainClient does not transparently reconnect a dead socket — a
retry over a closed connection re-fails fast. For resilience against a dropped
socket, front the client with a brain_db_sdk::Pool and borrow a fresh
connection.
Cancellation and deadlines
Neither client takes an external cancellation token; a call is bounded by its
configured deadline instead. Because both clients are async-native on Tokio
(there is no blocking/sync variant), you can also drop the future or wrap it in
tokio::time::timeout for ad-hoc cancellation.
Each request is bounded by the client timeout (default 30 s), set with
.with_timeout(..). On expiry the request is aborted and surfaces as a
BrainHttpError with status: 0 (a transport failure), never a panic.
use std::time::Duration;
use brain_db_sdk::BrainHttpClient;
let client = BrainHttpClient::localhost("my-api-key")
.with_timeout(Duration::from_secs(5));connect_timeout bounds the TCP connect and request_timeout bounds each
response read (defaults 10 s / 30 s), set on ClientConfig. A missed
deadline surfaces as BrainError::Timeout(Duration), which is_retryable
treats as retryable.
use std::net::SocketAddr;
use std::time::Duration;
use brain_db_sdk::{Auth, BrainClient, ClientConfig};
let addr: SocketAddr = "127.0.0.1:9090".parse()?;
let mut config = ClientConfig::new(Auth::Token(b"my-token".to_vec()));
config.request_timeout = Some(Duration::from_secs(5));
let client = BrainClient::connect_with(addr, config).await?;Was this page helpful?