Skip to main content

ruststream_fred/pipeline/
source.rs

1//! What a `.pipeline()` subscription opens: the form's own subscription, with a window.
2
3use std::fmt::{Debug, Formatter};
4use std::future::Future;
5use std::num::NonZeroUsize;
6use std::sync::Arc;
7
8use futures::Stream;
9#[cfg(feature = "asyncapi")]
10use ruststream::asyncapi::Bindings;
11use ruststream::{
12    AddressedCopies, BatchSubscriber, NamedCopies, RedeliveryAddress, RedeliveryAddressed,
13    RetryDeclaration, Seekable, Subscriber, SubscriptionSource,
14};
15
16use super::forms::{ListForm, PubSubForm, StreamForm};
17use super::window::{Form, Window};
18use super::{Pipelined, RoundMessage, WindowMode};
19use crate::broker::ConnectedRedisBroker;
20use crate::error::RedisError;
21use crate::list::{ListReader, RedisList};
22use crate::pubsub::{PubSubWire, RedisPubSub, RedisPubSubPattern};
23use crate::seek::RedisGroupSeeker;
24use crate::stream::RedisStream;
25use crate::subscriber::{PREFETCH, RedisSubscriber};
26
27/// The window's capacity on the single-delivery path: the read's `COUNT`.
28fn prefetch() -> usize {
29    usize::try_from(PREFETCH).unwrap_or(usize::MAX)
30}
31
32/// A pipelined subscription: the form's own subscriber and the window its deliveries settle in.
33///
34/// The window flushes what it still owes when the subscription stops.
35pub struct PipelinedSubscriber<Inner, F: Form> {
36    inner: Inner,
37    window: Arc<Window<F>>,
38}
39
40impl<Inner, F: Form> Debug for PipelinedSubscriber<Inner, F> {
41    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("PipelinedSubscriber")
43            .field("window", &self.window)
44            .finish_non_exhaustive()
45    }
46}
47
48impl<Inner, F: Form> PipelinedSubscriber<Inner, F> {
49    fn new(inner: Inner, window: Window<F>) -> Self {
50        Self {
51            inner,
52            window: Arc::new(window),
53        }
54    }
55}
56
57impl<Inner, F: Form> Drop for PipelinedSubscriber<Inner, F> {
58    fn drop(&mut self) {
59        // A subscription stops by being dropped, and a destructor cannot wait: the flush of what
60        // the window still owes runs on the broker's runtime, wherever the drop happens.
61        let window = Arc::clone(&self.window);
62        self.window.spawn(async move { window.drain().await });
63    }
64}
65
66impl Subscriber for PipelinedSubscriber<RedisSubscriber, StreamForm> {
67    type Message = RoundMessage<StreamForm>;
68    type Error = RedisError;
69
70    /// Yields one delivery per entry, each with a slot in the window.
71    ///
72    /// # Cancel safety
73    ///
74    /// As [`RedisSubscriber`]'s: entries fetched and not yet settled stay pending.
75    fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
76        self.inner.round_stream(&self.window)
77    }
78}
79
80impl BatchSubscriber for PipelinedSubscriber<RedisSubscriber, StreamForm> {
81    type Batch = Vec<RoundMessage<StreamForm>>;
82
83    /// Yields one batch per read, `size` being the read's `COUNT`; the batch is one segment of the
84    /// window, so its settles leave as one `XACK`.
85    ///
86    /// # Cancel safety
87    ///
88    /// As [`RedisSubscriber`]'s batches: entries fetched and not yet settled stay pending.
89    fn batches(
90        &mut self,
91        size: NonZeroUsize,
92    ) -> impl Stream<Item = Result<Self::Batch, Self::Error>> + Send + '_ {
93        self.inner.round_batches(&self.window, size)
94    }
95}
96
97/// Repositions the consumer group as the subscription without a window does, which is what a
98/// mount site's `start_at(..)` seeks through.
99impl Seekable for PipelinedSubscriber<RedisSubscriber, StreamForm> {
100    type Seeker = RedisGroupSeeker;
101
102    fn seeker(&self) -> RedisGroupSeeker {
103        self.inner.seeker()
104    }
105}
106
107/// A Pub/Sub subscription's dedicated reader, as a pipelined subscription holds it.
108pub struct ChannelReader(PubSubWire);
109
110impl Debug for ChannelReader {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("ChannelReader").finish_non_exhaustive()
113    }
114}
115
116impl Subscriber for PipelinedSubscriber<ChannelReader, PubSubForm> {
117    type Message = RoundMessage<PubSubForm>;
118    type Error = RedisError;
119
120    /// Yields one delivery per message, each with a slot in the window.
121    ///
122    /// # Cancel safety
123    ///
124    /// As a Pub/Sub subscription's: a message nobody is polling for is lost.
125    fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
126        self.inner.0.round_stream(&self.window)
127    }
128}
129
130impl Subscriber for PipelinedSubscriber<ListReader, ListForm> {
131    type Message = RoundMessage<ListForm>;
132    type Error = RedisError;
133
134    /// Yields one delivery per entry, each with a slot in the window, claiming the read's `COUNT`
135    /// at a time.
136    ///
137    /// # Cancel safety
138    ///
139    /// As [`RedisListSubscriber`](crate::RedisListSubscriber)'s: on a reliable list an entry
140    /// claimed and not settled stays on the processing list.
141    fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
142        self.inner.round_stream(&self.window, prefetch())
143    }
144}
145
146impl BatchSubscriber for PipelinedSubscriber<ChannelReader, PubSubForm> {
147    type Batch = Vec<RoundMessage<PubSubForm>>;
148
149    /// Yields a batch of the messages already arrived, up to `size`; the batch is one segment of
150    /// the window.
151    ///
152    /// # Cancel safety
153    ///
154    /// As a Pub/Sub subscription's: a message nobody is polling for is lost.
155    fn batches(
156        &mut self,
157        size: NonZeroUsize,
158    ) -> impl Stream<Item = Result<Self::Batch, Self::Error>> + Send + '_ {
159        self.inner.0.round_batches(&self.window, size)
160    }
161}
162
163impl BatchSubscriber for PipelinedSubscriber<ListReader, ListForm> {
164    type Batch = Vec<RoundMessage<ListForm>>;
165
166    /// Yields one batch per read of up to `size` entries; the batch is one segment of the window.
167    ///
168    /// # Cancel safety
169    ///
170    /// As a list subscription's batches: on a reliable list an entry claimed and not settled
171    /// stays on the processing list.
172    fn batches(
173        &mut self,
174        size: NonZeroUsize,
175    ) -> impl Stream<Item = Result<Self::Batch, Self::Error>> + Send + '_ {
176        self.inner.round_batches(&self.window, size)
177    }
178}
179
180/// The parts of `SubscriptionSource` every pipelined descriptor shares with the descriptor it
181/// wraps: its name, its retry declaration and its document.
182macro_rules! delegates {
183    ($broker:ty, $name:ident) => {
184        fn name(&self) -> &str {
185            self.descriptor().$name()
186        }
187
188        fn declare_retry(self, declaration: &RetryDeclaration) -> Self {
189            Self::wrap(SubscriptionSource::<$broker>::declare_retry(
190                self.into_descriptor(),
191                declaration,
192            ))
193        }
194
195        #[cfg(feature = "asyncapi")]
196        fn channel_bindings(&self) -> Bindings {
197            SubscriptionSource::<$broker>::channel_bindings(self.descriptor())
198        }
199    };
200}
201
202/// Where a pipelined descriptor's copies go: where the descriptor's go.
203macro_rules! addressed {
204    ($broker:ty, $descriptor:ty) => {
205        impl<Mode: WindowMode> RedeliveryAddressed<$broker> for Pipelined<$descriptor, Mode> {
206            fn redelivery_address(
207                &self,
208                connected: &$broker,
209            ) -> impl Future<Output = Result<RedeliveryAddress, RedisError>> {
210                self.descriptor().redelivery_address(connected)
211            }
212        }
213    };
214}
215
216impl<Mode: WindowMode> SubscriptionSource<ConnectedRedisBroker> for Pipelined<RedisStream, Mode> {
217    type Subscriber = PipelinedSubscriber<RedisSubscriber, StreamForm>;
218    type Copies = AddressedCopies;
219
220    delegates!(ConnectedRedisBroker, key);
221
222    async fn subscribe(
223        self,
224        connected: &ConnectedRedisBroker,
225    ) -> Result<Self::Subscriber, RedisError> {
226        let inner = connected.subscribe(self.into_descriptor()).await?;
227        let window = Window::new(
228            inner.round_form(),
229            inner.round_client(),
230            Mode::ATOMIC,
231            inner.key(),
232            prefetch(),
233            connected.window_owner()?,
234        );
235        Ok(PipelinedSubscriber::new(inner, window))
236    }
237}
238
239impl<Mode: WindowMode> SubscriptionSource<ConnectedRedisBroker> for Pipelined<RedisPubSub, Mode> {
240    type Subscriber = PipelinedSubscriber<ChannelReader, PubSubForm>;
241    type Copies = AddressedCopies;
242
243    delegates!(ConnectedRedisBroker, channel);
244
245    async fn subscribe(
246        self,
247        connected: &ConnectedRedisBroker,
248    ) -> Result<Self::Subscriber, RedisError> {
249        let channel = self.descriptor().channel().to_owned();
250        let inner = connected.open_pubsub(self.into_descriptor()).await?;
251        // Pub/Sub reads one message at a time, so the window leaves when nothing is in flight;
252        // the capacity only caps a window that never drains.
253        let window = Window::new(
254            PubSubForm,
255            inner.round_client(),
256            Mode::ATOMIC,
257            channel,
258            prefetch(),
259            connected.window_owner()?,
260        );
261        Ok(PipelinedSubscriber::new(ChannelReader(inner), window))
262    }
263}
264
265/// A pattern reads every channel its glob matches and names none a publish could reach, so the
266/// mount site names where a retry copy goes, as on the pattern subscription without a window.
267impl<Mode: WindowMode> SubscriptionSource<ConnectedRedisBroker>
268    for Pipelined<RedisPubSubPattern, Mode>
269{
270    type Subscriber = PipelinedSubscriber<ChannelReader, PubSubForm>;
271    type Copies = NamedCopies;
272
273    fn name(&self) -> &str {
274        self.descriptor().pattern()
275    }
276
277    async fn subscribe(
278        self,
279        connected: &ConnectedRedisBroker,
280    ) -> Result<Self::Subscriber, RedisError> {
281        let pattern = self.descriptor().pattern().to_owned();
282        let inner = connected
283            .open_pubsub_pattern(self.into_descriptor())
284            .await?;
285        // Pub/Sub reads one message at a time, as on a channel: the window leaves when nothing
286        // is in flight.
287        let window = Window::new(
288            PubSubForm,
289            inner.round_client(),
290            Mode::ATOMIC,
291            pattern,
292            prefetch(),
293            connected.window_owner()?,
294        );
295        Ok(PipelinedSubscriber::new(ChannelReader(inner), window))
296    }
297
298    #[cfg(feature = "asyncapi")]
299    fn channel_bindings(&self) -> Bindings {
300        SubscriptionSource::<ConnectedRedisBroker>::channel_bindings(self.descriptor())
301    }
302}
303
304impl<Mode: WindowMode> SubscriptionSource<ConnectedRedisBroker> for Pipelined<RedisList, Mode> {
305    type Subscriber = PipelinedSubscriber<ListReader, ListForm>;
306    type Copies = AddressedCopies;
307
308    delegates!(ConnectedRedisBroker, key);
309
310    async fn subscribe(
311        self,
312        connected: &ConnectedRedisBroker,
313    ) -> Result<Self::Subscriber, RedisError> {
314        let wire = connected.open_list(self.into_descriptor()).await?;
315        let window = Window::new(
316            wire.round_form(),
317            wire.round_client(),
318            Mode::ATOMIC,
319            wire.key(),
320            prefetch(),
321            connected.window_owner()?,
322        );
323        Ok(PipelinedSubscriber::new(ListReader::new(wire), window))
324    }
325}
326
327addressed!(ConnectedRedisBroker, RedisStream);
328addressed!(ConnectedRedisBroker, RedisList);
329addressed!(ConnectedRedisBroker, RedisPubSub);