pub struct WeirClient<S = DefaultTransport> { /* private fields */ }Expand description
A synchronous blocking client for one connection to the weir daemon.
The type parameter S is the underlying transport. The default is
DefaultTransport — a Unix domain socket on Unix. When the tls feature
is enabled, S can also be TlsStream (TCP + mutual TLS).
The underlying stream runs in blocking mode. Each method sends one request
frame and reads one response frame before returning. Requests are not
pipelined; for concurrent producers, create one WeirClient per thread.
Drop the client to close the connection. The daemon treats EOF as a clean disconnect.
Implementations§
Source§impl<S: Read + Write> WeirClient<S>
impl<S: Read + Write> WeirClient<S>
Sourcepub fn set_default_durability(&mut self, durability: Durability)
pub fn set_default_durability(&mut self, durability: Durability)
Sets the default durability tier used by push_default.
Sourcepub fn push(
&mut self,
payload: impl AsRef<[u8]>,
durability: Durability,
) -> Result<(), ClientError>
pub fn push( &mut self, payload: impl AsRef<[u8]>, durability: Durability, ) -> Result<(), ClientError>
Pushes payload to the daemon with the given durability tier.
Blocks until the daemon replies. On Ack the record is durably stored
according to the requested tier. On Nack returns the reason as
ClientError::Nack.
An Ack means the record is durably buffered at the requested tier —
not that it has reached the downstream sink yet (the daemon drains in
batches once a segment seals). See the crate-level “Ack vs. delivery” note.
The ack is per record, not a liveness probe for the next one. If the
daemon dies (or the socket is severed) between two pushes, the first
push after the failure can still return Ok — its frame was written and
acked, or the breakage isn’t observed until the next read/write — and only
the following push surfaces the broken pipe. So a single Ok does not
guarantee the connection is healthy for subsequent pushes. After any push
error, branch on ClientError::is_recoverable /
is_poisoned to decide whether to keep using this
client or drop and reconnect, rather than assuming a prior Ok proved the
connection good.
Sourcepub fn push_tracked(
&mut self,
payload: impl AsRef<[u8]>,
durability: Durability,
) -> Result<RecordCoordinate, ClientError>
pub fn push_tracked( &mut self, payload: impl AsRef<[u8]>, durability: Durability, ) -> Result<RecordCoordinate, ClientError>
Pushes payload and returns the RecordCoordinate the daemon assigned
it — where the record landed in the write-ahead buffer, and the id the
downstream sink will see for it.
Identical to push in every other respect: same durability
tiers, same local guards, same Nack reasons, same connection effects. It
sends PushTracked instead of Push and reads back an AckTracked.
§What the coordinate is for
The record_id is byte-identical to the
per-record idempotency key the drain hands the sink (the HTTP sink’s
Idempotency-Key: sha256:<hex> header, the S3 sink’s object name), so a
producer can reconcile what it sent against what the downstream received.
That is the question push alone cannot answer.
§What it is not
- Not a delivery receipt. Like
push, an ack means the record is durably buffered at the requested tier — not that it has reached the sink. The coordinate says where it will be read from, not that it has been read. - Not a gap-free sequence. The coordinate is a buffer address. Other producers’ records interleave, so one producer’s indices have holes.
- Not a durability upgrade. A
Bufferedpush gets a coordinate too, and can still be lost on power loss; the tier still decides that.
§Talking to a daemon that predates this
A daemon built before PushTracked existed rejects the frame with
NackReason::UnknownMessage and closes the connection — the protocol’s
existing permanent-error path for version skew. So
Err(ClientError::Nack(NackReason::UnknownMessage)) from this method
means “this daemon does not support tracked pushes”; fall back to
push on a fresh connection.
Sourcepub fn push_default(
&mut self,
payload: impl AsRef<[u8]>,
) -> Result<(), ClientError>
pub fn push_default( &mut self, payload: impl AsRef<[u8]>, ) -> Result<(), ClientError>
Pushes at the connection’s default durability tier.
Returns ClientError::NoDefaultDurability if no default was set via
set_default_durability (or via a
connect_with_default / connect_tls constructor that accepted a
default_durability).
Sourcepub fn push_batch(
&mut self,
records: &[impl AsRef<[u8]>],
durability: Durability,
) -> Result<BatchOutcome, ClientError>
pub fn push_batch( &mut self, records: &[impl AsRef<[u8]>], durability: Durability, ) -> Result<BatchOutcome, ClientError>
push_tracked at the connection’s default
durability tier.
Returns ClientError::NoDefaultDurability if no default was set.
Pushes N records in one round trip and reports each one’s fate.
Returns a BatchOutcome whose accepted[i] says whether record i is
durable at the requested tier. A partial result is Ok, not Err —
ClientError describes the connection, and after a partial batch the
connection is fine. Inspect the outcome; do not assume success from Ok.
§What a cleared bit means
Not “this record was never written”. It means not durable as of this reply — retry it, and expect that it may nonetheless have been written. weir is at-least-once, and a record whose ack timed out may still be committed by the flusher afterwards. A set bit is the strong statement (it inherits the crown invariant: acked ⇒ durable); a cleared bit is the weak one. Building exactly-once retry on cleared bits does not work.
Every validation failure — an empty record, one over the cap, a framing
disagreement — rejects the whole frame with a Nack and closes the
connection, exactly as a malformed single push does. So a bitmap only
ever reports transient, retryable failures.
A daemon predating PushBatch answers Nack(UnknownMessage) and closes;
that surfaces as a poisoning ClientError, so reconnect and use
push.
Sourcepub fn push_tracked_default(
&mut self,
payload: impl AsRef<[u8]>,
) -> Result<RecordCoordinate, ClientError>
pub fn push_tracked_default( &mut self, payload: impl AsRef<[u8]>, ) -> Result<RecordCoordinate, ClientError>
push_tracked at the connection’s default
durability tier.
Returns ClientError::NoDefaultDurability if no default was set.
Sourcepub fn health_check(&mut self) -> Result<(), ClientError>
pub fn health_check(&mut self) -> Result<(), ClientError>
Sends a HealthCheck frame and returns Ok(()) on a valid
HealthCheckResponse. Returns an error if the daemon is unreachable
or responds with a Nack.
Sourcepub fn is_poisoned(&self) -> bool
pub fn is_poisoned(&self) -> bool
Whether this client’s connection is dead and must be rebuilt.
Reports the union of the two ways a connection becomes unusable, so it is the
exact complement of is_recoverable for the
error that caused it:
- an indeterminate stream (the internal
poisonedflag) — a response-read failure/timeout, a protocol violation in the reply, or any stream desync (an unexpected daemon message type, an empty-payload Nack); leftover/unexpected bytes could be mis-read as a later reply (a false ack); - a connection-closing Nack (the internal
closed_after_nackflag) — every Nack reason except the transientInternalError, after which the daemon closes the connection.
In either state every method fails fast (it will not touch the socket again); the caller must drop this client and open a fresh connection.
A long-lived producer thread (e.g. one driven from an async runtime) should
check this after a failed call — or, equivalently, branch on
ClientError::is_recoverable — and rebuild the connection when it reports
poisoned / non-recoverable, rather than retrying on the dead client:
if let Err(e) = client.push(b"record", Durability::Durable) {
if !e.is_recoverable() || client.is_poisoned() {
client = reconnect(); // drop the dead client, rebuild
}
}See ClientError::is_recoverable for the per-error view of the same
distinction (recoverable = connection still usable; non-recoverable = drop
and reconnect). The two are kept equivalent: is_poisoned() is true after a
failed call exactly when that call’s error is non-recoverable.
Source§impl WeirClient<UnixStream>
impl WeirClient<UnixStream>
Sourcepub fn connect(path: impl AsRef<Path>) -> Result<Self, ClientError>
pub fn connect(path: impl AsRef<Path>) -> Result<Self, ClientError>
Opens a connection to the weir daemon’s Unix socket at path.
Sourcepub fn connect_with_default(
path: impl AsRef<Path>,
durability: Durability,
) -> Result<Self, ClientError>
pub fn connect_with_default( path: impl AsRef<Path>, durability: Durability, ) -> Result<Self, ClientError>
Opens a connection and sets a default durability tier in one step.
Equivalent to calling connect then
set_default_durability.
Sourcepub fn from_stream(stream: UnixStream) -> Self
pub fn from_stream(stream: UnixStream) -> Self
Wraps an already-connected UnixStream. Useful for callers that
manage their own connection setup (systemd socket activation,
pre-authenticated file descriptors passed from a parent process,
UnixStream::pair-based test harnesses).
Sourcepub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<()>
pub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<()>
Sets the read timeout on the underlying socket. None (the default)
blocks indefinitely.
Opt-in availability guard. By default every method blocks in the
response-read path (inside push /
health_check) waiting for the daemon’s Ack/Nack, so a
wedged daemon (hung flusher, SIGSTOP, half-open connection) would block
a producer’s hot path forever. With a read timeout set, a stalled reply
surfaces as a ClientError::Io timeout instead; the producer can retry
— the record may still have been durably written, which the at-least-once
contract covers. Pick a value comfortably above the daemon’s Durable ack
latency under load: the daemon’s own ACK_TIMEOUT is 30 s, so e.g.
45–60 s lets the daemon’s Nack win rather than racing it.
Sourcepub fn set_write_timeout(&self, timeout: Option<Duration>) -> Result<()>
pub fn set_write_timeout(&self, timeout: Option<Duration>) -> Result<()>
Sets the write timeout on the underlying socket. None (the default)
blocks indefinitely. See set_read_timeout for
the rationale — this bounds a stalled write_all of the request frame.
Source§impl WeirClient<TlsStream>
impl WeirClient<TlsStream>
Sourcepub fn connect_tls(
addr: impl ToSocketAddrs,
cfg: ClientTlsConfig<'_>,
) -> Result<Self, ClientError>
pub fn connect_tls( addr: impl ToSocketAddrs, cfg: ClientTlsConfig<'_>, ) -> Result<Self, ClientError>
Connects to a weir daemon’s TCP+mTLS listener at addr.
Performs a full mutual-TLS handshake: the client presents client_cert
(signed by the same CA the server trusts), and the server certificate is
validated against ca_cert. The server_name must match a SAN in the
server’s certificate.
Sourcepub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<()>
pub fn set_read_timeout(&self, timeout: Option<Duration>) -> Result<()>
Sets the read timeout on the underlying TCP socket. None (the default)
blocks indefinitely. Same opt-in availability rationale as the Unix
client’s set_read_timeout — a wedged
daemon would otherwise block a producer forever in read_response.
Sourcepub fn set_write_timeout(&self, timeout: Option<Duration>) -> Result<()>
pub fn set_write_timeout(&self, timeout: Option<Duration>) -> Result<()>
Sets the write timeout on the underlying TCP socket. None (the default)
blocks indefinitely. See set_read_timeout.