Skip to main content

WeirClient

Struct WeirClient 

Source
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>

Source

pub fn set_default_durability(&mut self, durability: Durability)

Sets the default durability tier used by push_default.

Source

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.

Source

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 Buffered push 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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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 poisoned flag) — 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_nack flag) — every Nack reason except the transient InternalError, 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>

Source

pub fn connect(path: impl AsRef<Path>) -> Result<Self, ClientError>

Opens a connection to the weir daemon’s Unix socket at path.

Source

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.

Source

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).

Source

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.

Source

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>

Source

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.

Source

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.

Source

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.

Trait Implementations§

Source§

impl<S> Debug for WeirClient<S>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<S> Freeze for WeirClient<S>
where S: Freeze,

§

impl<S> RefUnwindSafe for WeirClient<S>
where S: RefUnwindSafe,

§

impl<S> Send for WeirClient<S>
where S: Send,

§

impl<S> Sync for WeirClient<S>
where S: Sync,

§

impl<S> Unpin for WeirClient<S>
where S: Unpin,

§

impl<S> UnsafeUnpin for WeirClient<S>
where S: UnsafeUnpin,

§

impl<S> UnwindSafe for WeirClient<S>
where S: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.