p2panda_sync/manager/
event_stream.rs1use 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#[allow(clippy::type_complexity)]
50pub struct ManagerEventStream<T, E>
51where
52 T: Clone + Eq + StdHash + Send + 'static,
53 E: Extensions + Send + 'static,
54{
55 pub(crate) state: Option<ManagerEventStreamState<T, E>>,
57
58 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 !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}