Skip to main content

p2panda_sync/manager/
event_stream.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::fmt::Debug;
4use std::hash::Hash as StdHash;
5use std::pin::Pin;
6use std::task::{Context, Poll};
7
8use futures::channel::mpsc;
9use futures::stream::SelectAll;
10use futures::{SinkExt, Stream, StreamExt};
11use p2panda_core::traits::Digest;
12use p2panda_core::{Extensions, Hash};
13use tokio_stream::wrappers::BroadcastStream;
14use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
15use tracing::{debug, trace};
16
17use crate::dedup::DeduplicationBuffer;
18use crate::manager::{SessionStream, SessionTopicMap, ToTopicSync};
19use crate::protocols::TopicLogSyncEvent;
20use crate::{FromSync, ToSync};
21
22pub(crate) trait StreamDebug<Item>: Stream<Item = Item> + Send + Debug + 'static {}
23
24impl<T, Item> StreamDebug<Item> for T where T: Stream<Item = Item> + Send + Debug + 'static {}
25
26#[allow(clippy::type_complexity)]
27pub(crate) struct ManagerEventStreamState<T, E>
28where
29    T: Clone + Eq + StdHash + Send + 'static,
30    E: Extensions + Send + 'static,
31{
32    pub(crate) manager_rx: mpsc::Receiver<SessionStream<T, E>>,
33    pub(crate) session_rx_set:
34        SelectAll<Pin<Box<dyn StreamDebug<Option<FromSync<TopicLogSyncEvent<E>>>>>>>,
35    pub(crate) session_topic_map: SessionTopicMap<T, mpsc::Sender<ToTopicSync<E>>>,
36    pub(crate) dedup: DeduplicationBuffer<Hash>,
37}
38
39type FutureOutput<T, E> = (
40    ManagerEventStreamState<T, E>,
41    Option<FromSync<TopicLogSyncEvent<E>>>,
42);
43
44/// Event stream for a manager returned from SyncManager::subscribe().
45///
46/// Calling `next_event` on the manager event stream returns the next event in the event queue
47/// (combined events of all running sync sessions). If the event contains an operation then it
48/// will be forwarded on to any concurrently running sync sessions.
49#[allow(clippy::type_complexity)]
50pub struct ManagerEventStream<T, E>
51where
52    T: Clone + Eq + StdHash + Send + 'static,
53    E: Extensions + Send + 'static,
54{
55    /// Stream state.
56    pub(crate) state: Option<ManagerEventStreamState<T, E>>,
57
58    /// The current future being polled.
59    pub(crate) pending: Option<Pin<Box<dyn Future<Output = FutureOutput<T, E>> + Send>>>,
60}
61
62impl<T, E> ManagerEventStream<T, E>
63where
64    T: Clone + Debug + Eq + StdHash + Send + 'static,
65    E: Extensions + Send + 'static,
66{
67    async fn next_event(
68        mut state: ManagerEventStreamState<T, E>,
69    ) -> (
70        ManagerEventStreamState<T, E>,
71        Option<FromSync<TopicLogSyncEvent<E>>>,
72    ) {
73        loop {
74            tokio::select!(
75                biased;
76                item = state.manager_rx.next() => {
77                    let Some(manager_event) = item else {
78                        trace!("manager event stream closed");
79                        return (state, None)
80                    };
81                    trace!("manager event received: {manager_event:?}");
82                    let session_id = manager_event.session_id;
83                    state.session_topic_map.insert_with_topic(session_id, manager_event.topic, manager_event.live_tx);
84
85                    let stream = BroadcastStream::new(manager_event.event_rx);
86
87                    let stream =
88                        Box::pin(stream.map(Box::new(
89                            move |event: Result<TopicLogSyncEvent<E>, BroadcastStreamRecvError>| {
90                                event.ok().map(|event| FromSync {
91                                    session_id,
92                                    remote: manager_event.remote,
93                                    event,
94                                })
95                            },
96                        )));
97                    state.session_rx_set.push(stream);
98                }
99                Some(Some(from_sync)) = state.session_rx_set.next() => {
100                    trace!("from sync event received: {from_sync:?}");
101                    let session_id = from_sync.session_id();
102                    let event = from_sync.event();
103
104                    let operation = match event {
105                        TopicLogSyncEvent::OperationReceived{operation, ..} => Some(*operation.clone()),
106                        _ => return (state, Some(from_sync)),
107                    };
108
109                    if let Some(operation) = operation {
110                        let Some(topic) = state.session_topic_map.topic(session_id) else {
111                            debug!(session_id, "drop session: missing from topic map");
112                            state.session_topic_map.drop(session_id);
113                            continue;
114                        };
115                        let keys = state.session_topic_map.sessions(topic);
116                        let mut dropped = vec![];
117
118                        for id in keys {
119                            if id == session_id {
120                                continue;
121                            }
122
123                            let Some(tx) = state.session_topic_map.sender_mut(id) else {
124                                debug!(session_id = id, "drop session: channel unexpectedly closed");
125                                state.session_topic_map.drop(session_id);
126                                continue;
127                            };
128
129                            let result = tx.send(ToSync::Payload(operation.clone())).await;
130
131                            if result.is_err() {
132                                dropped.push(id);
133                            }
134                        }
135
136                        for id in dropped {
137                            debug!(session_id = id, "drop session: channel unexpectedly closed");
138                            state.session_topic_map.drop(id);
139                        }
140
141                        // If this operation was already present in the deduplication buffer then
142                        // it means we have received it before on this event stream and should not
143                        // return it again to any consumers.
144                        if !state.dedup.insert(operation.hash()) {
145                            continue;
146                        }
147                    }
148
149                    return (state, Some(from_sync))
150                }
151            )
152        }
153    }
154}
155
156impl<T, E> Unpin for ManagerEventStream<T, E>
157where
158    T: Clone + Debug + Eq + StdHash + Send + 'static,
159    E: Extensions + Send + 'static,
160{
161}
162
163impl<T, E> Stream for ManagerEventStream<T, E>
164where
165    T: Clone + Debug + Eq + StdHash + Send + 'static,
166    E: Extensions + Send + 'static,
167{
168    type Item = FromSync<TopicLogSyncEvent<E>>;
169
170    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
171        if self.pending.is_none() {
172            let fut = Box::pin(ManagerEventStream::next_event(
173                self.state.take().expect("state is not None"),
174            ));
175            self.pending = Some(fut);
176        }
177
178        let fut = self.pending.as_mut().unwrap();
179        match fut.as_mut().poll(cx) {
180            Poll::Pending => Poll::Pending,
181            Poll::Ready((state, item)) => {
182                self.pending = None;
183                self.state.replace(state);
184                Poll::Ready(item)
185            }
186        }
187    }
188}