1use futures::{Stream, TryFuture, TryStream, stream::FusedStream};
30use pin_project::pin_project;
31use std::{
32 future::Future,
33 pin::Pin,
34 task::{Context, Poll, ready},
35};
36use tonic::Status;
37
38use crate::streams::IntoInner;
39
40#[pin_project]
41struct StreamEstablish<F> {
43 #[pin]
44 inner: F,
45}
46
47impl<F> StreamEstablish<F> {
48 fn new(inner: F) -> Self {
49 Self { inner }
50 }
51}
52
53impl<F> Future for StreamEstablish<F>
54where
55 F: TryFuture<Error = Status>,
56{
57 type Output = Result<F::Ok, Status>;
58
59 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
60 use Poll::*;
61 let this = self.as_mut().project();
62 let response = ready!(this.inner.try_poll(cx));
63 let response = response.inspect_err(|e| {
64 tracing::error!("Error during grpc-web subscription establishment {e}");
65 })?;
66 Ready(Ok(response))
67 }
68}
69
70#[pin_project(project = ProjectStream)]
71enum StreamState<F, S> {
73 NotStarted {
74 #[pin]
75 future: StreamEstablish<F>,
76 },
77 Started {
78 #[pin]
79 stream: S,
80 },
81 Terminated,
82}
83
84#[pin_project]
85pub struct NonBlockingWebStream<F, S> {
86 #[pin]
87 state: StreamState<F, S>,
88}
89
90impl<F, S> NonBlockingWebStream<F, S>
91where
92 F: TryFuture<Error = Status>,
93{
94 pub fn new(request: F) -> Self {
95 Self {
96 state: StreamState::NotStarted {
97 future: StreamEstablish::new(request),
98 },
99 }
100 }
101
102 pub fn started(stream: S) -> Self {
104 Self {
105 state: StreamState::Started { stream },
106 }
107 }
108}
109
110impl<F, S> Stream for NonBlockingWebStream<F, S>
111where
112 S: TryStream<Error = Status>,
113 F: TryFuture<Error = Status>,
114 F::Ok: IntoInner<Out = S>,
115{
116 type Item = Result<S::Ok, Status>;
117
118 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
119 use ProjectStream::*;
120 let mut this = self.as_mut().project();
121 match this.state.as_mut().project() {
122 NotStarted { future } => {
123 match ready!(future.poll(cx)) {
124 Ok(stream) => {
125 this.state.set(StreamState::Started {
126 stream: stream.into_inner(),
127 });
128 }
129 Err(e) => {
130 this.state.set(StreamState::Terminated);
131 return Poll::Ready(Some(Err(e)));
132 }
133 }
134 tracing::trace!("stream ready, polling for the first time...");
135 cx.waker().wake_by_ref();
136 Poll::Pending
137 }
138 Started { mut stream } => {
139 let next = stream.as_mut().try_poll_next(cx);
140 if let Poll::Ready(None) = next {
141 this.state.set(StreamState::Terminated);
142 }
143 next
144 }
145 Terminated => Poll::Ready(None),
146 }
147 }
148}
149
150impl<F, S> FusedStream for NonBlockingWebStream<F, S>
151where
152 F: TryFuture<Error = Status>,
153 S: TryStream<Error = Status> + FusedStream,
154 F::Ok: IntoInner<Out = S>,
155{
156 fn is_terminated(&self) -> bool {
157 match &self.state {
158 StreamState::Started { stream } => stream.is_terminated(),
159 StreamState::Terminated => true,
160 _ => false,
161 }
162 }
163}
164
165impl<F, S> std::fmt::Debug for NonBlockingWebStream<F, S> {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
167 match self.state {
168 StreamState::NotStarted { .. } => write!(f, "not started"),
169 StreamState::Started { .. } => write!(f, "started"),
170 StreamState::Terminated => write!(f, "terminated"),
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use futures::{Stream, stream};
178 use futures_test::future::FutureTestExt;
179 use prost::bytes::Bytes;
180 use tonic::{Response, Streaming};
181
182 use super::*;
183
184 struct TestStream;
185 impl Stream for TestStream {
186 type Item = Result<Response<Bytes>, Status>;
187
188 fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
189 unreachable!()
190 }
191 }
192
193 impl FusedStream for TestStream {
194 fn is_terminated(&self) -> bool {
195 unreachable!()
196 }
197 }
198
199 impl<T> From<Streaming<T>> for TestStream {
200 fn from(_: Streaming<T>) -> Self {
201 unreachable!()
202 }
203 }
204
205 struct MockFut;
206 impl Future for MockFut {
207 type Output = Result<TestStream, Status>;
208
209 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
210 unimplemented!()
211 }
212 }
213
214 impl IntoInner for MockFut {
215 type Out = TestStream;
216
217 fn into_inner(self) -> Self::Out {
218 todo!()
219 }
220 }
221
222 #[xmtp_common::test]
223 fn handles_err_on_establish() {
224 let stream: NonBlockingWebStream<_, TestStream> =
225 NonBlockingWebStream::new(futures::future::ready({
226 Err::<MockFut, _>(Status::internal("test error"))
229 }));
230 futures::pin_mut!(stream);
231
232 assert!(matches!(stream.state, StreamState::NotStarted { .. }));
233 let cx = futures::task::noop_waker();
234 let mut cx = std::task::Context::from_waker(&cx);
235 assert!(matches!(
236 stream.as_mut().poll_next(&mut cx),
237 Poll::Ready(Some(Err(_)))
238 ));
239
240 assert!(FusedStream::is_terminated(&stream));
241 assert!(matches!(
242 stream.as_mut().poll_next(&mut cx),
243 Poll::Ready(None)
244 ));
245 }
246
247 #[xmtp_common::test]
248 fn happy_path_future() {
249 let fut = futures::future::ready(Ok(()));
250 let fut = fut.pending_once();
251 let fut = StreamEstablish::new(fut);
252 futures::pin_mut!(fut);
253 let mut context = futures_test::task::noop_context();
254 assert_eq!(
255 Poll::Pending,
256 fut.as_mut().poll(&mut context).map(Result::unwrap)
257 );
258 assert_eq!(Poll::Ready(()), fut.poll(&mut context).map(Result::unwrap));
259 }
260
261 struct FakeFuture<T>(T);
262
263 impl<T> FakeFuture<T> {
264 fn inner(self: Pin<&mut Self>) -> Pin<&mut T> {
265 unsafe { self.map_unchecked_mut(|s| &mut s.0) }
267 }
268 }
269
270 impl<T> Future for FakeFuture<T>
271 where
272 T: TryFuture<Error = Status>,
273 {
274 type Output = Result<T::Ok, Status>;
275
276 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
277 self.inner().try_poll(cx)
278 }
279 }
280
281 struct FakeStream<T>(T);
282
283 impl<T> FakeStream<T> {
284 fn inner(self: Pin<&mut Self>) -> Pin<&mut T> {
285 unsafe { self.map_unchecked_mut(|s| &mut s.0) }
287 }
288 }
289
290 impl<T> Stream for FakeStream<T>
291 where
292 T: TryStream<Error = Status>,
293 {
294 type Item = Result<T::Ok, Status>;
295
296 fn poll_next(
297 self: Pin<&mut Self>,
298 cx: &mut Context<'_>,
299 ) -> Poll<Option<Result<T::Ok, Status>>> {
300 self.inner().try_poll_next(cx)
301 }
302 }
303
304 impl<T> IntoInner for FakeStream<T> {
305 type Out = FakeStream<T>;
306
307 fn into_inner(self) -> Self::Out {
308 self
309 }
310 }
311
312 impl<T: TryStream<Error = Status>> FusedStream for FakeStream<T> {
313 fn is_terminated(&self) -> bool {
314 unreachable!()
315 }
316 }
317
318 fn item<T>(i: T) -> Result<T, Status> {
319 Ok(i)
320 }
321
322 #[xmtp_common::test]
323 fn establish_changes_state_to_started() {
324 let s = FakeStream(stream::iter(vec![item(0usize), item(1), item(2)]));
325 let fut = futures::future::ready(Ok(s));
326 let fut = FakeFuture(fut);
327 let fut = fut.pending_once();
328 let s =
329 NonBlockingWebStream::<_, FakeStream<stream::Iter<std::vec::IntoIter<_>>>>::new(fut);
330
331 futures::pin_mut!(s);
332 let mut context = futures_test::task::noop_context();
333 assert_eq!(
334 Poll::Pending,
335 s.as_mut()
336 .poll_next(&mut context)
337 .map(Option::unwrap)
338 .map(Result::unwrap)
339 );
340 assert!(matches!(s.state, StreamState::NotStarted { .. }));
341 assert_eq!(
342 Poll::Pending,
343 s.as_mut()
344 .poll_next(&mut context)
345 .map(Option::unwrap)
346 .map(Result::unwrap)
347 );
348 assert!(matches!(s.state, StreamState::Started { .. }));
349 for i in 0..3 {
350 assert_eq!(
351 Poll::Ready(i),
352 s.as_mut()
353 .poll_next(&mut context)
354 .map(Option::unwrap)
355 .map(Result::unwrap)
356 );
357 }
358 assert_eq!(
360 Poll::Ready(None),
361 s.as_mut()
362 .poll_next(&mut context)
363 .map(|o| o.map(Result::unwrap))
364 );
365 assert_eq!(
366 Poll::Ready(None),
367 s.as_mut()
368 .poll_next(&mut context)
369 .map(|o| o.map(Result::unwrap))
370 );
371 assert!(matches!(s.state, StreamState::Terminated));
373 }
374}