///|
/// Reusable immutable connection settings for read-only retry workflows.
pub(all) struct ClientConfig {
host : String
port : Int32
export_name : Bytes
structured : Bool
allocation : Bool
timeout_ms : Int32
}
///|
pub fn ClientConfig::new(
host : String,
port : Int32,
export_name : Bytes,
structured~ : Bool = false,
allocation~ : Bool = false,
timeout_ms~ : Int32 = 30000,
) -> ClientConfig raise {
if port <= 0 ||
port > 65535 ||
export_name.length() > 4096 ||
(allocation && !structured) ||
timeout_ms <= 0 {
raise InvalidArgument("invalid client connection settings")
}
{ host, port, export_name, structured, allocation, timeout_ms, }
}
///|
pub async fn ClientConfig::connect(self : ClientConfig) -> Client {
Client::connect(
self.host,
self.port,
self.export_name,
structured=self.structured,
allocation=self.allocation,
timeout_ms=self.timeout_ms,
)
}
///|
fn retryable_read_error(error : Error) -> Bool {
match error {
InvalidArgument(_) | Server(_) | Protocol(_) => false
_ => true
}
}
///|
/// Read once or reconnect and retry after a transport failure.
///
/// This function is safe only for a stable export: a reconnect can observe a
/// different version of mutable storage. It never retries writes, flushes,
/// trims, or write-zeroes because their outcome can be unknown after a lost
/// connection. `retries` counts additional attempts after the first one.
pub async fn ClientConfig::read_at_retry(
self : ClientConfig,
offset : UInt64,
length : Int32,
retries~ : Int32 = 1,
) -> Bytes {
if retries < 0 || retries > 16 {
raise InvalidArgument("retry count must be between 0 and 16")
}
let mut attempt = 0
while true {
let result = try {
let client = self.connect()
defer client.close()
let data = client.read_at(offset, length)
// The read has succeeded. A failed disconnect must not trigger a repeat.
client.disconnect() catch {
_ => ()
}
Ok(data)
} catch {
error => Err(error)
}
match result {
Ok(data) => return data
Err(error) => {
if attempt >= retries || !retryable_read_error(error) {
raise error
}
attempt += 1
}
}
} nobreak {
fail("unreachable retry loop exit")
}
}