1use crate::builder::read::ReadRows;
18use crate::model::ReadRowsResponse;
19use crate::read::retry_policy::RetryableErrors;
20use crate::{Error, Result};
21use google_cloud_gax::backoff_policy::BackoffPolicy;
22use google_cloud_gax::exponential_backoff::{ExponentialBackoff, ExponentialBackoffBuilder};
23use google_cloud_gax::retry_policy::{RetryPolicy, RetryPolicyArg, RetryPolicyExt};
24use google_cloud_gax::retry_result::RetryResult;
25use google_cloud_gax::retry_state::RetryState;
26use google_cloud_gax::streaming::ResponseStream;
27use std::sync::Arc;
28use std::time::Duration;
29use tokio::time::Instant;
30
31const INITIAL_DELAY: Duration = Duration::from_millis(100);
32const MAXIMUM_DELAY: Duration = Duration::from_secs(60);
33const SCALING_FACTOR: f64 = 1.3;
34const MAX_ATTEMPTS: u32 = 10;
35
36fn default_retry_policy() -> Arc<dyn RetryPolicy> {
40 Arc::new(RetryableErrors.with_attempt_limit(MAX_ATTEMPTS))
41}
42
43fn default_backoff_policy() -> ExponentialBackoff {
49 ExponentialBackoffBuilder::new()
50 .with_initial_delay(INITIAL_DELAY)
51 .with_maximum_delay(MAXIMUM_DELAY)
52 .with_scaling(SCALING_FACTOR)
53 .build()
54 .expect("hardcoded value guaranteed to be valid")
55}
56
57#[derive(Debug)]
90pub(crate) enum ReaderState {
91 Connecting {
93 offset: i64,
94 retry_state: RetryState,
95 },
96 Backoff {
98 offset: i64,
99 retry_state: RetryState,
100 deadline: Instant,
101 },
102 Reading {
108 offset: i64,
109 retry_state: Option<RetryState>,
110 stream: ResponseStream<ReadRowsResponse>,
111 },
112 Terminated(Option<Error>),
114}
115
116#[derive(Debug)]
119pub struct Reader {
120 retry_policy: Arc<dyn RetryPolicy>,
121 backoff_policy: ExponentialBackoff,
122 request: ReadRows,
123 state: ReaderState,
126}
127
128impl Reader {
129 pub(crate) fn new(request: ReadRows) -> Self {
130 let offset = request.0.request.offset;
131 Self {
132 retry_policy: default_retry_policy(),
133 backoff_policy: default_backoff_policy(),
134 request,
135 state: ReaderState::Connecting {
136 offset,
137 retry_state: RetryState::new(true),
138 },
139 }
140 }
141
142 pub fn with_retry_policy(mut self, v: impl Into<RetryPolicyArg>) -> Self {
159 self.retry_policy = v.into().into();
160 self
161 }
162
163 fn handle_error(&self, offset: i64, mut retry_state: RetryState, err: Error) -> ReaderState {
164 retry_state.attempt_count += 1;
165 let prev_err = match self.retry_policy.on_error(&retry_state, err) {
166 RetryResult::Permanent(e) | RetryResult::Exhausted(e) => {
167 return ReaderState::Terminated(Some(e));
168 }
169 RetryResult::Continue(e) => e,
170 };
171
172 let delay = self.backoff_policy.on_failure(&retry_state);
173 if self
174 .retry_policy
175 .remaining_time(&retry_state)
176 .is_some_and(|remaining| remaining <= delay)
177 {
178 return ReaderState::Terminated(Some(Error::exhausted(prev_err)));
179 }
180 ReaderState::Backoff {
181 offset,
182 retry_state,
183 deadline: Instant::now() + delay,
184 }
185 }
186
187 async fn step(&mut self) -> Option<ReadRowsResponse> {
190 match &mut self.state {
191 ReaderState::Connecting {
192 offset,
193 retry_state,
194 } => {
195 let offset = *offset;
196 let retry_state = retry_state.clone();
197 let req = self.request.clone().set_offset(offset);
198 match req.send().await {
199 Ok(stream) => {
200 self.state = ReaderState::Reading {
201 offset,
202 retry_state: Some(retry_state),
203 stream,
204 };
205 }
206 Err(err) => {
207 self.state = self.handle_error(offset, retry_state, err);
208 }
209 }
210 None
211 }
212 ReaderState::Backoff {
213 offset,
214 retry_state,
215 deadline,
216 } => {
217 tokio::time::sleep_until(*deadline).await;
218 self.state = ReaderState::Connecting {
219 offset: *offset,
220 retry_state: retry_state.clone(),
221 };
222 None
223 }
224 ReaderState::Reading {
225 offset,
226 retry_state,
227 stream,
228 } => match stream.next().await {
229 Some(Ok(response)) => {
230 *offset += response.row_count;
231 *retry_state = None;
234 Some(response)
235 }
236 Some(Err(err)) => {
237 let offset = *offset;
238 let retry_state = retry_state.take().unwrap_or_else(|| RetryState::new(true));
239 self.state = self.handle_error(offset, retry_state, err);
240 None
241 }
242 None => {
243 self.state = ReaderState::Terminated(None);
244 None
245 }
246 },
247 ReaderState::Terminated(_) => None,
248 }
249 }
250
251 pub async fn next(&mut self) -> Option<Result<ReadRowsResponse>> {
254 loop {
255 if let Some(response) = self.step().await {
256 return Some(Ok(response));
257 }
258 if let ReaderState::Terminated(err) = &mut self.state {
259 return err.take().map(Err);
260 }
261 }
262 }
263}
264
265impl ReadRows {
266 pub fn into_reader(self) -> Reader {
281 Reader::new(self)
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::client::Read;
289 use crate::model::ReadRowsRequest;
290 use google_cloud_gax::error::rpc::{Code, Status};
291 use google_cloud_gax::options::RequestOptions;
292 use std::error::Error as _;
293 use tokio::sync::mpsc;
294
295 mockall::mock! {
296 #[derive(Debug)]
297 ReadStub {}
298 impl crate::stub::Read for ReadStub {
299 async fn read_rows(
300 &self,
301 req: ReadRowsRequest,
302 options: RequestOptions,
303 ) -> Result<ResponseStream<ReadRowsResponse>>;
304 }
305 }
306
307 mockall::mock! {
308 #[derive(Debug)]
309 pub RetryPolicy {}
310 impl RetryPolicy for RetryPolicy {
311 fn on_error(&self, state: &RetryState, error: Error) -> RetryResult;
312 fn remaining_time(&self, state: &RetryState) -> Option<Duration>;
313 }
314 }
315
316 fn mock_retry_policy() -> MockRetryPolicy {
318 let mut retry = MockRetryPolicy::new();
319 retry.expect_remaining_time().returning(|_| None);
320 retry
321 }
322
323 fn transient_error() -> Error {
324 Error::service(
325 Status::default()
326 .set_code(Code::Unavailable)
327 .set_message("transient failure"),
328 )
329 }
330
331 fn permanent_error() -> Error {
332 Error::service(
333 Status::default()
334 .set_code(Code::PermissionDenied)
335 .set_message("permission denied"),
336 )
337 }
338
339 #[test]
340 fn traits() {
341 static_assertions::assert_impl_all!(Reader: Send, std::fmt::Debug);
342 }
343
344 #[tokio::test]
345 async fn step_connecting_to_reading() -> anyhow::Result<()> {
346 let mut mock = MockReadStub::new();
347 mock.expect_read_rows().once().returning(|req, _| {
348 assert_eq!(req.read_stream, "streams/1");
349 assert_eq!(req.offset, 42);
350 let (_tx, rx) = mpsc::channel(1);
351 Ok(ResponseStream::from(rx))
352 });
353
354 let mut retry = mock_retry_policy();
355 retry.expect_on_error().never();
356
357 let client = Read::from_stub(mock);
358 let req = client.read_rows().set_read_stream("streams/1");
359 let mut reader = Reader::new(req).with_retry_policy(retry);
360
361 let initial_retry = RetryState::new(true).set_attempt_count(2_u32);
362 reader.state = ReaderState::Connecting {
363 offset: 42,
364 retry_state: initial_retry,
365 };
366 let resp = reader.step().await;
367 assert!(resp.is_none());
368 let ReaderState::Reading {
369 offset,
370 retry_state: Some(retry_state),
371 ..
372 } = &reader.state
373 else {
374 anyhow::bail!(
375 "expected Reading state with Some(retry_state), got: {:?}",
376 reader.state
377 );
378 };
379 assert_eq!(*offset, 42);
380 assert_eq!(retry_state.attempt_count, 2);
381 Ok(())
382 }
383
384 #[tokio::test(start_paused = true)]
385 async fn step_connecting_retryable_error() -> anyhow::Result<()> {
386 let mut mock = MockReadStub::new();
387 mock.expect_read_rows()
388 .once()
389 .returning(|_, _| Err(transient_error()));
390
391 let mut retry = mock_retry_policy();
392 retry
393 .expect_on_error()
394 .withf(|state, e| {
395 state.attempt_count == 2 && e.status().is_some_and(|s| s.code == Code::Unavailable)
396 })
397 .once()
398 .returning(|_, e| RetryResult::Continue(e));
399
400 let client = Read::from_stub(mock);
401 let req = client.read_rows().set_read_stream("streams/1");
402 let mut reader = Reader::new(req).with_retry_policy(retry);
403
404 let initial_retry = RetryState::new(true).set_attempt_count(1_u32);
405 reader.state = ReaderState::Connecting {
406 offset: 10,
407 retry_state: initial_retry,
408 };
409 let now = Instant::now();
410 let resp = reader.step().await;
411 assert!(resp.is_none());
412 let ReaderState::Backoff {
413 offset,
414 retry_state,
415 deadline,
416 } = &reader.state
417 else {
418 anyhow::bail!("expected Backoff state, got: {:?}", reader.state);
419 };
420 assert_eq!(*offset, 10);
421 assert_eq!(retry_state.attempt_count, 2);
422 assert!(*deadline >= now);
423 assert!(*deadline <= now + INITIAL_DELAY.mul_f64(SCALING_FACTOR));
424 Ok(())
425 }
426
427 #[tokio::test]
428 async fn step_connecting_permanent_error() -> anyhow::Result<()> {
429 let mut mock = MockReadStub::new();
430 mock.expect_read_rows()
431 .once()
432 .returning(|_, _| Err(permanent_error()));
433
434 let mut retry = mock_retry_policy();
435 retry
436 .expect_on_error()
437 .withf(|state, e| {
438 state.attempt_count == 1
439 && e.status().is_some_and(|s| s.code == Code::PermissionDenied)
440 })
441 .once()
442 .returning(|_, e| RetryResult::Permanent(e));
443
444 let client = Read::from_stub(mock);
445 let req = client.read_rows().set_read_stream("streams/1");
446 let mut reader = Reader::new(req).with_retry_policy(retry);
447
448 reader.state = ReaderState::Connecting {
449 offset: 0,
450 retry_state: RetryState::new(true),
451 };
452 let resp = reader.step().await;
453 assert!(resp.is_none());
454 let ReaderState::Terminated(Some(err)) = &reader.state else {
455 anyhow::bail!("expected Terminated(Some(_)), got: {:?}", reader.state);
456 };
457 assert_eq!(err.status().map(|s| s.code), Some(Code::PermissionDenied));
458 Ok(())
459 }
460
461 #[tokio::test]
462 async fn step_connecting_deadline_exhausted() -> anyhow::Result<()> {
463 let mut mock = MockReadStub::new();
464 mock.expect_read_rows()
465 .once()
466 .returning(|_, _| Err(transient_error()));
467
468 let mut retry = MockRetryPolicy::new();
469 retry
470 .expect_remaining_time()
471 .returning(|_| Some(Duration::ZERO));
472 retry
473 .expect_on_error()
474 .once()
475 .returning(|_, e| RetryResult::Continue(e));
476
477 let client = Read::from_stub(mock);
478 let req = client.read_rows().set_read_stream("streams/1");
479 let mut reader = Reader::new(req).with_retry_policy(retry);
480
481 reader.state = ReaderState::Connecting {
482 offset: 0,
483 retry_state: RetryState::new(true),
484 };
485 let resp = reader.step().await;
486 assert!(resp.is_none());
487 let ReaderState::Terminated(Some(err)) = &reader.state else {
488 anyhow::bail!("expected Terminated(Some(_)), got: {:?}", reader.state);
489 };
490 assert!(err.is_exhausted(), "{err:?}");
491 let last_error = err.source().expect("the error should have a source");
492 assert!(
493 last_error.to_string().contains("transient failure"),
494 "{last_error:?}"
495 );
496 Ok(())
497 }
498
499 #[tokio::test(start_paused = true)]
500 async fn step_backoff_to_connecting() -> anyhow::Result<()> {
501 let mut mock = MockReadStub::new();
502 mock.expect_read_rows().never();
503 let mut retry = mock_retry_policy();
504 retry.expect_on_error().never();
505
506 let client = Read::from_stub(mock);
507 let req = client.read_rows().set_read_stream("streams/1");
508 let mut reader = Reader::new(req).with_retry_policy(retry);
509
510 let deadline = Instant::now() + Duration::from_millis(500);
511 reader.state = ReaderState::Backoff {
512 offset: 42,
513 retry_state: RetryState::new(true).set_attempt_count(2_u32),
514 deadline,
515 };
516 let resp = reader.step().await;
517 assert!(resp.is_none());
518 assert_eq!(Instant::now(), deadline);
519 let ReaderState::Connecting {
520 offset,
521 retry_state,
522 } = &reader.state
523 else {
524 anyhow::bail!("expected Connecting state, got: {:?}", reader.state);
525 };
526 assert_eq!(*offset, 42);
527 assert_eq!(retry_state.attempt_count, 2);
528 Ok(())
529 }
530
531 #[tokio::test]
532 async fn step_first_message_clears_retry_state() -> anyhow::Result<()> {
533 let mut mock = MockReadStub::new();
534 mock.expect_read_rows().never();
535 let mut retry = mock_retry_policy();
536 retry.expect_on_error().never();
537
538 let client = Read::from_stub(mock);
539 let req = client.read_rows().set_read_stream("streams/1");
540 let mut reader = Reader::new(req).with_retry_policy(retry);
541
542 let (tx, rx) = mpsc::channel(1);
543 tx.send(Ok(ReadRowsResponse::new().set_row_count(5)))
544 .await?;
545 let stream = ResponseStream::from(rx);
546
547 reader.state = ReaderState::Reading {
548 offset: 10,
549 retry_state: Some(RetryState::new(true).set_attempt_count(3_u32)),
550 stream,
551 };
552 let resp = reader.step().await.expect("expected Some(response)");
553 let ReaderState::Reading {
554 offset,
555 retry_state,
556 ..
557 } = &reader.state
558 else {
559 anyhow::bail!("expected Reading state, got: {:?}", reader.state);
560 };
561 assert_eq!(resp.row_count, 5);
562 assert_eq!(*offset, 15);
563 assert!(
564 retry_state.is_none(),
565 "expected retry_state to be cleared to None after first message, got: {retry_state:?}"
566 );
567 Ok(())
568 }
569
570 #[tokio::test(start_paused = true)]
571 async fn step_first_message_retryable_error_retains_retry_state() -> anyhow::Result<()> {
572 let mut mock = MockReadStub::new();
573 mock.expect_read_rows().never();
574
575 let mut retry = mock_retry_policy();
576 retry
577 .expect_on_error()
578 .withf(|state, e| {
579 state.attempt_count == 3 && e.status().is_some_and(|s| s.code == Code::Unavailable)
580 })
581 .once()
582 .returning(|_, e| RetryResult::Continue(e));
583
584 let client = Read::from_stub(mock);
585 let req = client.read_rows().set_read_stream("streams/1");
586 let mut reader = Reader::new(req).with_retry_policy(retry);
587
588 let (tx, rx) = mpsc::channel(1);
589 tx.send(Err(transient_error())).await?;
590 let stream = ResponseStream::from(rx);
591
592 let prior_retry = RetryState::new(true).set_attempt_count(2_u32);
594 reader.state = ReaderState::Reading {
595 offset: 7,
596 retry_state: Some(prior_retry),
597 stream,
598 };
599 let now = Instant::now();
600 let resp = reader.step().await;
601 assert!(resp.is_none());
602 let ReaderState::Backoff {
603 offset,
604 retry_state,
605 deadline,
606 } = &reader.state
607 else {
608 anyhow::bail!("expected Backoff state, got: {:?}", reader.state);
609 };
610 assert_eq!(*offset, 7);
611 assert_eq!(retry_state.attempt_count, 3);
612 assert!(*deadline >= now);
613 assert!(*deadline <= now + INITIAL_DELAY.mul_f64(SCALING_FACTOR.powi(2)));
614 Ok(())
615 }
616
617 #[tokio::test]
618 async fn step_reading_to_reading() -> anyhow::Result<()> {
619 let mut mock = MockReadStub::new();
620 mock.expect_read_rows().never();
621 let mut retry = mock_retry_policy();
622 retry.expect_on_error().never();
623
624 let client = Read::from_stub(mock);
625 let req = client.read_rows().set_read_stream("streams/1");
626 let mut reader = Reader::new(req).with_retry_policy(retry);
627
628 let (tx, rx) = mpsc::channel(1);
629 tx.send(Ok(ReadRowsResponse::new().set_row_count(3)))
630 .await?;
631 let stream = ResponseStream::from(rx);
632
633 reader.state = ReaderState::Reading {
634 offset: 5,
635 retry_state: None,
636 stream,
637 };
638 let resp = reader.step().await.expect("expected Some(response)");
639 let ReaderState::Reading {
640 offset,
641 retry_state,
642 ..
643 } = &reader.state
644 else {
645 anyhow::bail!("expected Reading state, got: {:?}", reader.state);
646 };
647 assert_eq!(resp.row_count, 3);
648 assert_eq!(*offset, 8);
649 assert!(retry_state.is_none());
650 Ok(())
651 }
652
653 #[tokio::test(start_paused = true)]
654 async fn step_reading_retryable_error_resets_retry_state() -> anyhow::Result<()> {
655 let mut mock = MockReadStub::new();
656 mock.expect_read_rows().never();
657
658 let mut retry = mock_retry_policy();
659 retry
660 .expect_on_error()
661 .withf(|state, e| {
662 state.attempt_count == 1 && e.status().is_some_and(|s| s.code == Code::Unavailable)
664 })
665 .once()
666 .returning(|_, e| RetryResult::Continue(e));
667
668 let client = Read::from_stub(mock);
669 let req = client.read_rows().set_read_stream("streams/1");
670 let mut reader = Reader::new(req).with_retry_policy(retry);
671
672 let (tx, rx) = mpsc::channel(1);
673 tx.send(Err(transient_error())).await?;
674 let stream = ResponseStream::from(rx);
675
676 reader.state = ReaderState::Reading {
677 offset: 12,
678 retry_state: None,
679 stream,
680 };
681 let now = Instant::now();
682 let resp = reader.step().await;
683 assert!(resp.is_none());
684 let ReaderState::Backoff {
685 offset,
686 retry_state,
687 deadline,
688 } = &reader.state
689 else {
690 anyhow::bail!("expected Backoff state, got: {:?}", reader.state);
691 };
692 assert_eq!(*offset, 12);
693 assert_eq!(retry_state.attempt_count, 1);
694 assert!(*deadline >= now);
695 assert!(*deadline <= now + INITIAL_DELAY);
696 Ok(())
697 }
698
699 #[tokio::test]
700 async fn step_reading_permanent_error_to_terminated() -> anyhow::Result<()> {
701 let mut mock = MockReadStub::new();
702 mock.expect_read_rows().never();
703
704 let mut retry = mock_retry_policy();
705 retry
706 .expect_on_error()
707 .withf(|state, e| {
708 state.attempt_count == 1
709 && e.status().is_some_and(|s| s.code == Code::PermissionDenied)
710 })
711 .once()
712 .returning(|_, e| RetryResult::Permanent(e));
713
714 let client = Read::from_stub(mock);
715 let req = client.read_rows().set_read_stream("streams/1");
716 let mut reader = Reader::new(req).with_retry_policy(retry);
717
718 let (tx, rx) = mpsc::channel(1);
719 tx.send(Err(permanent_error())).await?;
720 let stream = ResponseStream::from(rx);
721
722 reader.state = ReaderState::Reading {
723 offset: 0,
724 retry_state: None,
725 stream,
726 };
727 let resp = reader.step().await;
728 assert!(resp.is_none());
729 let ReaderState::Terminated(Some(err)) = &reader.state else {
730 anyhow::bail!("expected Terminated(Some(_)), got: {:?}", reader.state);
731 };
732 assert_eq!(err.status().map(|s| s.code), Some(Code::PermissionDenied));
733 Ok(())
734 }
735
736 #[tokio::test]
737 async fn step_reading_eof_to_terminated() -> anyhow::Result<()> {
738 let mut mock = MockReadStub::new();
739 mock.expect_read_rows().never();
740
741 let client = Read::from_stub(mock);
742 let req = client.read_rows().set_read_stream("streams/1");
743 let mut reader = Reader::new(req);
744
745 let (tx, rx) = mpsc::channel(1);
746 drop(tx);
747 let stream = ResponseStream::from(rx);
748
749 reader.state = ReaderState::Reading {
750 offset: 0,
751 retry_state: None,
752 stream,
753 };
754 let resp = reader.step().await;
755 assert!(resp.is_none());
756 assert!(
757 matches!(reader.state, ReaderState::Terminated(None)),
758 "expected Terminated(None), got: {:?}",
759 reader.state
760 );
761 Ok(())
762 }
763
764 #[tokio::test]
765 async fn step_terminated_stays_terminated() -> anyhow::Result<()> {
766 let mut mock = MockReadStub::new();
767 mock.expect_read_rows().never();
768
769 let client = Read::from_stub(mock);
770 let req = client.read_rows().set_read_stream("streams/1");
771 let mut reader = Reader::new(req);
772
773 reader.state = ReaderState::Terminated(None);
774 assert!(reader.step().await.is_none());
775 assert!(
776 matches!(reader.state, ReaderState::Terminated(None)),
777 "expected Terminated(None), got: {:?}",
778 reader.state
779 );
780
781 reader.state = ReaderState::Terminated(Some(permanent_error()));
782 assert!(reader.step().await.is_none());
783 let ReaderState::Terminated(Some(err)) = &reader.state else {
784 anyhow::bail!("expected Terminated(Some(_)), got: {:?}", reader.state);
785 };
786 assert_eq!(err.status().map(|s| s.code), Some(Code::PermissionDenied));
787 Ok(())
788 }
789
790 #[tokio::test]
791 async fn read_rows_success() -> anyhow::Result<()> {
792 let mut mock = MockReadStub::new();
793 mock.expect_read_rows().once().returning(|req, _| {
794 assert_eq!(req.read_stream, "streams/1");
795 assert_eq!(req.offset, 0);
796 let (tx, rx) = mpsc::channel(4);
797 tokio::spawn(async move {
798 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(5))).await;
799 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(3))).await;
800 });
801 Ok(ResponseStream::from(rx))
802 });
803
804 let client = Read::from_stub(mock);
805 let req = client.read_rows().set_read_stream("streams/1");
806 let mut reader = Reader::new(req);
807
808 let r1 = reader.next().await.transpose()?.expect("row batch 1");
809 assert_eq!(r1.row_count, 5);
810 let r2 = reader.next().await.transpose()?.expect("row batch 2");
811 assert_eq!(r2.row_count, 3);
812 assert!(reader.next().await.is_none());
813 assert!(reader.next().await.is_none());
814 Ok(())
815 }
816
817 #[tokio::test(start_paused = true)]
818 async fn reconnect_mid_stream_resumes_at_offset() -> anyhow::Result<()> {
819 let mut seq = mockall::Sequence::new();
820 let mut mock = MockReadStub::new();
821
822 mock.expect_read_rows()
824 .once()
825 .in_sequence(&mut seq)
826 .returning(|req, _| {
827 assert_eq!(req.offset, 0);
828 let (tx, rx) = mpsc::channel(4);
829 tokio::spawn(async move {
830 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(5))).await;
831 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(3))).await;
832 let _ = tx.send(Err(transient_error())).await;
833 });
834 Ok(ResponseStream::from(rx))
835 });
836
837 mock.expect_read_rows()
839 .once()
840 .in_sequence(&mut seq)
841 .returning(|req, _| {
842 assert_eq!(req.offset, 8);
843 let (tx, rx) = mpsc::channel(4);
844 tokio::spawn(async move {
845 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(2))).await;
846 });
847 Ok(ResponseStream::from(rx))
848 });
849
850 let client = Read::from_stub(mock);
851 let req = client.read_rows().set_read_stream("streams/1");
852 let mut reader = Reader::new(req);
853
854 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 5);
855 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 3);
856 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 2);
857 assert!(reader.next().await.is_none());
858 Ok(())
859 }
860
861 #[tokio::test(start_paused = true)]
862 async fn with_offset_starts_and_reconnects_from_initial_offset() -> anyhow::Result<()> {
863 let mut seq = mockall::Sequence::new();
864 let mut mock = MockReadStub::new();
865
866 mock.expect_read_rows()
867 .once()
868 .in_sequence(&mut seq)
869 .returning(|req, _| {
870 assert_eq!(req.offset, 1_000);
871 let (tx, rx) = mpsc::channel(4);
872 tokio::spawn(async move {
873 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(25))).await;
874 let _ = tx
875 .send(Err(Error::service(
876 Status::default()
877 .set_code(Code::ResourceExhausted)
878 .set_message("rate limited"),
879 )))
880 .await;
881 });
882 Ok(ResponseStream::from(rx))
883 });
884
885 mock.expect_read_rows()
886 .once()
887 .in_sequence(&mut seq)
888 .returning(|req, _| {
889 assert_eq!(req.offset, 1_025);
890 let (tx, rx) = mpsc::channel(4);
891 tokio::spawn(async move {
892 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(10))).await;
893 });
894 Ok(ResponseStream::from(rx))
895 });
896
897 let client = Read::from_stub(mock);
898 let mut reader = client
899 .read_rows()
900 .set_read_stream("streams/1")
901 .set_offset(1_000)
902 .into_reader();
903
904 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 25);
905 if let ReaderState::Reading { offset, .. } = &reader.state {
906 assert_eq!(*offset, 1025);
907 } else {
908 panic!("expected reader state to be Reading");
909 }
910 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 10);
911 if let ReaderState::Reading { offset, .. } = &reader.state {
912 assert_eq!(*offset, 1035);
913 } else {
914 panic!("expected reader state to be Reading");
915 }
916 assert!(reader.next().await.is_none());
917 Ok(())
918 }
919
920 #[tokio::test]
921 async fn next_is_cancel_safe_while_waiting_for_stream() -> anyhow::Result<()> {
922 let mut mock = MockReadStub::new();
923 let (tx, rx) = mpsc::channel(4);
924
925 mock.expect_read_rows()
926 .once()
927 .return_once(move |_, _| Ok(ResponseStream::from(rx)));
928
929 let client = Read::from_stub(mock);
930 let mut reader = client
931 .read_rows()
932 .set_read_stream("streams/1")
933 .into_reader();
934
935 tx.send(Ok(ReadRowsResponse::new().set_row_count(4)))
936 .await?;
937 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 4);
938
939 let timed_out = tokio::time::timeout(Duration::from_millis(10), reader.next()).await;
941 assert!(timed_out.is_err(), "expected timeout while stream is idle");
942
943 tx.send(Ok(ReadRowsResponse::new().set_row_count(6)))
946 .await?;
947 drop(tx);
948
949 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 6);
950 assert!(reader.next().await.is_none());
951 Ok(())
952 }
953
954 #[tokio::test(start_paused = true)]
955 async fn next_is_cancel_safe_during_backoff() -> anyhow::Result<()> {
956 let mut mock = MockReadStub::new();
957
958 mock.expect_read_rows().once().returning(|req, _| {
960 assert_eq!(req.offset, 4);
961 let (tx, rx) = mpsc::channel(4);
962 tokio::spawn(async move {
963 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(6))).await;
964 });
965 Ok(ResponseStream::from(rx))
966 });
967
968 let client = Read::from_stub(mock);
969 let mut reader = client
970 .read_rows()
971 .set_read_stream("streams/1")
972 .into_reader();
973
974 let start = Instant::now();
975 let backoff_delay = Duration::from_secs(10);
976 reader.state = ReaderState::Backoff {
977 offset: 4,
978 retry_state: RetryState::new(true).set_attempt_count(1_u32),
979 deadline: start + backoff_delay,
980 };
981
982 let timed_out = tokio::time::timeout(Duration::from_secs(3), reader.next()).await;
984 assert!(timed_out.is_err(), "expected timeout during backoff");
985 assert!(
986 matches!(reader.state, ReaderState::Backoff { .. }),
987 "expected Reader to remain in Backoff state after cancellation, got: {:?}",
988 reader.state
989 );
990
991 let timed_out = tokio::time::timeout(Duration::from_secs(3), reader.next()).await;
993 assert!(
994 timed_out.is_err(),
995 "expected second timeout during remaining backoff"
996 );
997 assert!(
998 matches!(reader.state, ReaderState::Backoff { .. }),
999 "expected Reader to remain in Backoff state after second cancellation, got: {:?}",
1000 reader.state
1001 );
1002
1003 let r2 = reader.next().await.transpose()?.expect("row batch");
1005 assert_eq!(r2.row_count, 6);
1006 assert_eq!(Instant::now().duration_since(start), backoff_delay);
1007 assert!(reader.next().await.is_none());
1008 Ok(())
1009 }
1010
1011 #[tokio::test]
1012 async fn permanent_error_terminates_immediately() -> anyhow::Result<()> {
1013 let mut mock = MockReadStub::new();
1014 mock.expect_read_rows().once().returning(|_, _| {
1015 let (tx, rx) = mpsc::channel(2);
1016 tokio::spawn(async move {
1017 let _ = tx.send(Ok(ReadRowsResponse::new().set_row_count(5))).await;
1018 let _ = tx.send(Err(permanent_error())).await;
1019 });
1020 Ok(ResponseStream::from(rx))
1021 });
1022
1023 let client = Read::from_stub(mock);
1024 let req = client.read_rows().set_read_stream("streams/1");
1025 let mut reader = Reader::new(req);
1026
1027 assert_eq!(reader.next().await.transpose()?.unwrap().row_count, 5);
1028 let err = reader
1029 .next()
1030 .await
1031 .expect("should return error")
1032 .expect_err("should be permanent error");
1033 assert_eq!(err.status().map(|s| s.code), Some(Code::PermissionDenied));
1034 assert!(reader.next().await.is_none());
1035 Ok(())
1036 }
1037}