pub struct PendingAckQueue { /* private fields */ }Expand description
Fixed-capacity circular buffer of pending acks. Decouples TCP receives
from journal fsync by allowing up to cap batches to be submitted to
the journal stage before any ack is sent. Acks are flushed in FIFO
order as the journal cursor advances.
The queue never refuses a push and never makes the caller wait: at
capacity it coarsens instead (see PendingAckQueue::push). A
replica receiver that blocked here would stop receiving — and stop
producing the in-memory acks the primary’s disk+ram gate depends on —
for as long as its own fsync took, transplanting the replica’s disk
tail into the primary’s client latency.
A replica that is durably slower than the wire is still bounded, just further downstream: its input ring fills, the publish stalls, the TCP window closes, and the primary’s replication ring evicts it into a resync. This queue only decides ack granularity.
Box<[PendingAck]> rather than Vec<PendingAck>: the capacity is
fixed at construction; a slice avoids the Vec’s capacity field on
every push/pop. cap is constrained to a power of two so the
modulo on head + len lowers to a single AND.
Implementations§
Source§impl PendingAckQueue
impl PendingAckQueue
Sourcepub fn new(cap: usize) -> PendingAckQueue
pub fn new(cap: usize) -> PendingAckQueue
cap must be a power of two and ≥ 1.
pub fn is_full(&self) -> bool
pub fn is_empty(&self) -> bool
pub fn len(&self) -> usize
Sourcepub fn merged(&self) -> u64
pub fn merged(&self) -> u64
How many pushes have been merged into the newest entry because
the queue was full — see Self::push.
Sourcepub fn push(&mut self, journal_target: u64, acked_sequence: u64)
pub fn push(&mut self, journal_target: u64, acked_sequence: u64)
Record a pending ack. Never blocks and never drops progress.
When the queue is full the entry is merged into the newest one:
(t_old, s_old) + (t_new, s_new) → (max t, max s). Both cursors
are monotonic — the ring position a batch ends at and the primary
sequence it covers only grow — so the merged entry releases
s_new when the journal cursor reaches t_new, exactly the
condition the un-merged entry would have had. What is lost is
granularity: s_old is no longer acked at t_old, it waits for
t_new. That is strictly conservative (the ack is later, never
earlier, and never covers a sequence that is not durable), and it
is what lets the receiver keep receiving while its own journal
stage is behind.
Sourcepub fn pop_ready(
&mut self,
journal_cursor: &CachePadded<Atomic<u64>>,
) -> Option<u64>
pub fn pop_ready( &mut self, journal_cursor: &CachePadded<Atomic<u64>>, ) -> Option<u64>
Pop acks for all batches where the journal cursor has caught up.
Non-blocking — returns None immediately if the oldest pending
batch isn’t durable yet. Returns the highest acked sequence
among the flushed entries.
Sourcepub fn pop_oldest_blocking(
&mut self,
journal_cursor: &CachePadded<Atomic<u64>>,
wait: WaitStrategy,
abort: &Atomic<bool>,
) -> Option<u64>
pub fn pop_oldest_blocking( &mut self, journal_cursor: &CachePadded<Atomic<u64>>, wait: WaitStrategy, abort: &Atomic<bool>, ) -> Option<u64>
Block until the oldest pending ack is durable, then pop all
ready entries. Returns the highest acked sequence, or None if
abort flipped while waiting — the journal stage has died and
the cursor will never advance; the caller must tear the session
down instead of waiting forever.
Exit-path helper (via Self::pop_all_blocking): the streaming
receive path never blocks on durability, it merges instead.
Sourcepub fn pop_all_blocking(
&mut self,
journal_cursor: &CachePadded<Atomic<u64>>,
wait: WaitStrategy,
abort: &Atomic<bool>,
) -> Option<u64>
pub fn pop_all_blocking( &mut self, journal_cursor: &CachePadded<Atomic<u64>>, wait: WaitStrategy, abort: &Atomic<bool>, ) -> Option<u64>
Block until ALL pending acks are durable (or abort flips).
Returns the highest acked sequence, or None if the queue was
already empty or the first wait aborted.