rustis/client/client_tracking_invalidation_stream.rs
1use crate::{network::PushReceiver, resp::BulkString};
2use futures_util::{Stream, StreamExt};
3use std::{
4 pin::Pin,
5 task::{Context, Poll},
6};
7use tracing::warn;
8
9pub struct ClientTrackingInvalidationStream {
10 receiver: PushReceiver,
11}
12
13impl ClientTrackingInvalidationStream {
14 pub(crate) fn new(receiver: PushReceiver) -> Self {
15 Self { receiver }
16 }
17
18 /// Number of invalidation messages dropped so far because this stream fell
19 /// behind its memory budget.
20 ///
21 /// **A non-zero value means the reader no longer knows which keys are
22 /// stale.** Unlike a pub/sub message, a lost invalidation is not merely
23 /// missing data: acting on the remaining ones would leave the dropped keys
24 /// cached and served forever. A consumer that observes this counter move
25 /// must discard everything it cached, which is what the `Cache` (feature `client-cache`)
26 /// does — the same response it already has for invalidations lost across a
27 /// reconnection.
28 pub fn dropped_messages(&self) -> usize {
29 self.receiver.dropped_messages()
30 }
31}
32
33impl Stream for ClientTrackingInvalidationStream {
34 /// Redis keys are binary-safe, hence [`BulkString`] rather than `String`:
35 /// a key that is not valid UTF-8 must still reach the consumer.
36 type Item = Vec<BulkString>;
37
38 fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
39 let this = self.get_mut();
40
41 // An undecodable event must not end the stream: the consumer would stop
42 // polling and never learn of another invalidation, leaving every cache
43 // built on top of it serving stale data for good. Skip it and keep
44 // reading instead.
45 loop {
46 let Poll::Ready(response) = this.receiver.poll_next_unpin(cx) else {
47 return Poll::Pending;
48 };
49
50 let Some(response) = response else {
51 return Poll::Ready(None);
52 };
53
54 match response {
55 Ok(response) => match response.to::<((), Vec<BulkString>)>() {
56 Ok((_invalidate, keys)) => return Poll::Ready(Some(keys)),
57 Err(e) => warn!("Cannot decode a client tracking invalidation: {e}"),
58 },
59 Err(e) => warn!("Error while receiving a client tracking invalidation: {e}"),
60 }
61 }
62 }
63}