1use std::collections::HashSet;
13use std::sync::{Arc, RwLock};
14
15use anyhow::Result;
16use tokio::sync::watch;
17use uuid::Uuid;
18
19use crate::BlockId;
20
21use super::cancel::{CancelConfirmation, CancelStateUpdater, CancellationToken};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub struct TransferId(Uuid);
26
27impl TransferId {
28 pub fn new() -> Self {
30 TransferId(Uuid::new_v4())
31 }
32
33 pub fn as_uuid(&self) -> Uuid {
35 self.0
36 }
37}
38
39impl Default for TransferId {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl std::fmt::Display for TransferId {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(f, "{}", self.0)
48 }
49}
50
51impl From<Uuid> for TransferId {
52 fn from(uuid: Uuid) -> Self {
53 TransferId(uuid)
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum TransferStatus {
60 Evaluating,
62 Queued,
64 Transferring,
66 Complete,
68 Cancelled,
70 Failed,
72}
73
74impl TransferStatus {
75 pub fn is_terminal(&self) -> bool {
77 matches!(
78 self,
79 TransferStatus::Complete | TransferStatus::Cancelled | TransferStatus::Failed
80 )
81 }
82
83 pub fn is_active(&self) -> bool {
85 !self.is_terminal()
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct TransferResult {
92 pub id: TransferId,
94 pub status: TransferStatus,
96 pub passed_blocks: Vec<BlockId>,
98 pub completed_blocks: Vec<BlockId>,
100 pub failed_blocks: Vec<BlockId>,
102 pub filtered_blocks: Vec<BlockId>,
104 pub error: Option<String>,
106}
107
108#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
114pub struct TransferProgressCounts {
115 pub passed: usize,
117 pub completed: usize,
119 pub failed: usize,
121}
122
123impl TransferProgressCounts {
124 pub fn settled(self) -> usize {
126 self.completed.saturating_add(self.failed)
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct TransferProgressCursor {
136 transfer_id: TransferId,
137 passed: usize,
138 completed: usize,
139 failed: usize,
140}
141
142#[derive(Debug, Clone, Default, PartialEq, Eq)]
144pub struct TransferProgressDelta {
145 pub passed_blocks: Vec<BlockId>,
147 pub completed_blocks: Vec<BlockId>,
149 pub failed_blocks: Vec<BlockId>,
151}
152
153impl TransferProgressDelta {
154 pub fn is_empty(&self) -> bool {
156 self.passed_blocks.is_empty()
157 && self.completed_blocks.is_empty()
158 && self.failed_blocks.is_empty()
159 }
160}
161
162#[derive(Debug)]
163struct TransferProgress {
164 input_blocks: Vec<BlockId>,
165 policy_evaluated: bool,
166 passed_blocks: Vec<BlockId>,
167 completed_blocks: Vec<BlockId>,
168 failed_blocks: Vec<BlockId>,
169}
170
171impl TransferProgress {
172 fn new(input_blocks: Vec<BlockId>) -> Self {
173 Self {
174 input_blocks,
175 policy_evaluated: false,
176 passed_blocks: Vec::new(),
177 completed_blocks: Vec::new(),
178 failed_blocks: Vec::new(),
179 }
180 }
181
182 fn counts(&self) -> TransferProgressCounts {
183 TransferProgressCounts {
184 passed: self.passed_blocks.len(),
185 completed: self.completed_blocks.len(),
186 failed: self.failed_blocks.len(),
187 }
188 }
189}
190
191#[derive(Clone)]
198pub struct TransferHandle {
199 id: TransferId,
200 status_rx: watch::Receiver<TransferStatus>,
201 progress: Arc<RwLock<TransferProgress>>,
202 progress_rx: watch::Receiver<TransferProgressCounts>,
203 cancel_token: CancellationToken,
204 result_rx: watch::Receiver<Option<TransferResult>>,
205}
206
207impl TransferHandle {
208 pub fn id(&self) -> TransferId {
210 self.id
211 }
212
213 pub fn status(&self) -> TransferStatus {
215 *self.status_rx.borrow()
216 }
217
218 pub fn passed_blocks(&self) -> Vec<BlockId> {
220 self.progress
221 .read()
222 .expect("transfer progress lock poisoned")
223 .passed_blocks
224 .clone()
225 }
226
227 pub fn completed_blocks(&self) -> Vec<BlockId> {
229 self.progress
230 .read()
231 .expect("transfer progress lock poisoned")
232 .completed_blocks
233 .clone()
234 }
235
236 pub fn failed_blocks(&self) -> Vec<BlockId> {
238 self.progress
239 .read()
240 .expect("transfer progress lock poisoned")
241 .failed_blocks
242 .clone()
243 }
244
245 pub fn remaining_blocks(&self) -> Vec<BlockId> {
247 let progress = self
248 .progress
249 .read()
250 .expect("transfer progress lock poisoned");
251 if !progress.policy_evaluated {
252 return progress.input_blocks.clone();
253 }
254
255 let settled: HashSet<_> = progress
256 .completed_blocks
257 .iter()
258 .chain(&progress.failed_blocks)
259 .copied()
260 .collect();
261 progress
262 .passed_blocks
263 .iter()
264 .filter(|id| !settled.contains(id))
265 .copied()
266 .collect()
267 }
268
269 pub fn progress_counts(&self) -> TransferProgressCounts {
271 *self.progress_rx.borrow()
272 }
273
274 pub fn new_progress_cursor(&self) -> TransferProgressCursor {
276 TransferProgressCursor {
277 transfer_id: self.id,
278 passed: 0,
279 completed: 0,
280 failed: 0,
281 }
282 }
283
284 pub fn consume_progress(&self, cursor: &mut TransferProgressCursor) -> TransferProgressDelta {
287 assert_eq!(
288 cursor.transfer_id, self.id,
289 "transfer progress cursor used with a different handle"
290 );
291 let progress = self
292 .progress
293 .read()
294 .expect("transfer progress lock poisoned");
295 assert!(cursor.passed <= progress.passed_blocks.len());
296 assert!(cursor.completed <= progress.completed_blocks.len());
297 assert!(cursor.failed <= progress.failed_blocks.len());
298
299 let delta = TransferProgressDelta {
300 passed_blocks: progress.passed_blocks[cursor.passed..].to_vec(),
301 completed_blocks: progress.completed_blocks[cursor.completed..].to_vec(),
302 failed_blocks: progress.failed_blocks[cursor.failed..].to_vec(),
303 };
304 cursor.passed = progress.passed_blocks.len();
305 cursor.completed = progress.completed_blocks.len();
306 cursor.failed = progress.failed_blocks.len();
307 delta
308 }
309
310 pub fn is_complete(&self) -> bool {
312 self.status().is_terminal()
313 }
314
315 pub fn cancel(&self) -> CancelConfirmation {
327 self.cancel_token.request();
328 self.cancel_token.wait_confirmed()
329 }
330
331 pub fn is_cancelled(&self) -> bool {
333 self.cancel_token.is_requested()
334 }
335
336 pub async fn wait(&mut self) -> Result<TransferResult> {
340 loop {
342 {
343 let result = self.result_rx.borrow();
344 if let Some(r) = result.as_ref() {
345 return Ok(r.clone());
346 }
347 }
348
349 if self.result_rx.changed().await.is_err() {
350 return Err(anyhow::anyhow!("Transfer channel closed unexpectedly"));
352 }
353 }
354 }
355
356 pub fn subscribe_status(&self) -> watch::Receiver<TransferStatus> {
358 self.status_rx.clone()
359 }
360
361 pub fn subscribe_progress(&self) -> watch::Receiver<TransferProgressCounts> {
363 self.progress_rx.clone()
364 }
365}
366
367impl std::fmt::Debug for TransferHandle {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 let progress = self.progress_counts();
370 f.debug_struct("TransferHandle")
371 .field("id", &self.id)
372 .field("status", &self.status())
373 .field("passed_count", &progress.passed)
374 .field("completed_count", &progress.completed)
375 .field("failed_count", &progress.failed)
376 .field("remaining_count", &self.remaining_blocks().len())
377 .finish()
378 }
379}
380
381#[allow(dead_code)]
383pub(crate) struct TransferState {
384 pub(crate) id: TransferId,
385 pub(crate) status: TransferStatus,
387 progress: Arc<RwLock<TransferProgress>>,
390 pub(crate) in_flight: HashSet<BlockId>,
392 pub(crate) filtered_out: Vec<BlockId>,
394 pub(crate) error: Option<String>,
396 pub(crate) notifiers: TransferNotifiers,
398 pub(crate) cancel_updater: CancelStateUpdater,
400 pub(crate) total_expected_blocks: usize,
402 pub(crate) blocks_processed: usize,
404 pub(crate) precondition: Option<velo::EventHandle>,
409}
410
411#[allow(dead_code)]
412impl TransferState {
413 pub(crate) fn new(id: TransferId, input_blocks: Vec<BlockId>) -> (Self, TransferHandle) {
415 let (status_tx, status_rx) = watch::channel(TransferStatus::Evaluating);
416 let progress = Arc::new(RwLock::new(TransferProgress::new(input_blocks)));
417 let (progress_tx, progress_rx) = watch::channel(TransferProgressCounts::default());
418 let (result_tx, result_rx) = watch::channel(None);
419 let (cancel_token, cancel_updater) = CancellationToken::new();
420
421 let notifiers = TransferNotifiers {
422 status_tx,
423 progress_tx,
424 result_tx,
425 };
426
427 let state = TransferState {
428 id,
429 status: TransferStatus::Evaluating,
430 progress: progress.clone(),
431 in_flight: HashSet::new(),
432 filtered_out: Vec::new(),
433 error: None,
434 notifiers,
435 cancel_updater,
436 total_expected_blocks: 0, blocks_processed: 0,
438 precondition: None, };
440
441 let handle = TransferHandle {
442 id,
443 status_rx,
444 progress,
445 progress_rx,
446 cancel_token,
447 result_rx,
448 };
449
450 (state, handle)
451 }
452
453 pub(crate) fn is_cancel_requested(&self) -> bool {
455 self.cancel_updater.is_requested()
456 }
457
458 pub(crate) fn set_status(&mut self, status: TransferStatus) {
460 self.status = status;
461 let _ = self.notifiers.status_tx.send(status);
462 }
463
464 pub(crate) fn add_passed(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
466 let counts = {
467 let mut progress = self
468 .progress
469 .write()
470 .expect("transfer progress lock poisoned");
471 progress.policy_evaluated = true;
472 progress.passed_blocks.extend(block_ids);
473 progress.counts()
474 };
475 let _ = self.notifiers.progress_tx.send(counts);
476 }
477
478 pub(crate) fn add_filtered(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
480 self.filtered_out.extend(block_ids);
481 }
482
483 pub(crate) fn mark_in_flight(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
485 self.in_flight.extend(block_ids);
486 }
487
488 pub(crate) fn mark_completed(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
490 let completed: Vec<_> = block_ids.into_iter().collect();
491 for id in &completed {
492 self.in_flight.remove(id);
493 }
494 let counts = {
495 let mut progress = self
496 .progress
497 .write()
498 .expect("transfer progress lock poisoned");
499 progress.completed_blocks.extend(completed);
500 progress.counts()
501 };
502 let _ = self.notifiers.progress_tx.send(counts);
503 }
504
505 pub(crate) fn mark_failed(&mut self, block_ids: impl IntoIterator<Item = BlockId>) {
507 let failed: Vec<_> = block_ids.into_iter().collect();
508 for id in &failed {
509 self.in_flight.remove(id);
510 }
511 let counts = {
512 let mut progress = self
513 .progress
514 .write()
515 .expect("transfer progress lock poisoned");
516 progress.failed_blocks.extend(failed);
517 progress.counts()
518 };
519 let _ = self.notifiers.progress_tx.send(counts);
520 }
521
522 pub(crate) fn set_error(&mut self, error: String) {
524 self.error = Some(error);
525 self.set_status(TransferStatus::Failed);
526 self.finalize();
527 }
528
529 pub(crate) fn set_cancelled(&mut self) {
531 self.set_status(TransferStatus::Cancelled);
532 self.cancel_updater.set_confirmed();
533 self.finalize();
534 }
535
536 pub(crate) fn set_complete(&mut self) {
538 self.set_status(TransferStatus::Complete);
539 self.finalize();
540 }
541
542 fn finalize(&mut self) {
544 let progress = self
545 .progress
546 .read()
547 .expect("transfer progress lock poisoned");
548 let result = TransferResult {
549 id: self.id,
550 status: self.status,
551 passed_blocks: progress.passed_blocks.clone(),
552 completed_blocks: progress.completed_blocks.clone(),
553 failed_blocks: progress.failed_blocks.clone(),
554 filtered_blocks: self.filtered_out.clone(),
555 error: self.error.clone(),
556 };
557 let _ = self.notifiers.result_tx.send(Some(result));
558 }
559
560 pub(crate) fn progress_counts(&self) -> TransferProgressCounts {
561 self.progress
562 .read()
563 .expect("transfer progress lock poisoned")
564 .counts()
565 }
566
567 pub(crate) fn in_flight_count(&self) -> usize {
569 self.in_flight.len()
570 }
571
572 pub(crate) fn begin_draining(&self) {
574 self.cancel_updater.set_draining(self.in_flight.len());
575 }
576
577 pub(crate) fn update_draining(&self) {
579 self.cancel_updater.update_draining(self.in_flight.len());
580 }
581}
582
583#[allow(dead_code)]
585pub(crate) struct TransferNotifiers {
586 pub(crate) status_tx: watch::Sender<TransferStatus>,
587 pub(crate) progress_tx: watch::Sender<TransferProgressCounts>,
588 pub(crate) result_tx: watch::Sender<Option<TransferResult>>,
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594
595 #[test]
596 fn test_transfer_id() {
597 let id1 = TransferId::new();
598 let id2 = TransferId::new();
599 assert_ne!(id1, id2);
600 }
601
602 #[test]
603 fn test_transfer_status() {
604 assert!(!TransferStatus::Evaluating.is_terminal());
605 assert!(!TransferStatus::Queued.is_terminal());
606 assert!(!TransferStatus::Transferring.is_terminal());
607 assert!(TransferStatus::Complete.is_terminal());
608 assert!(TransferStatus::Cancelled.is_terminal());
609 assert!(TransferStatus::Failed.is_terminal());
610 }
611
612 #[test]
613 fn test_transfer_state_creation() {
614 let id = TransferId::new();
615 let blocks = vec![1, 2, 3];
616 let (state, handle) = TransferState::new(id, blocks.clone());
617
618 assert_eq!(state.id, id);
619 assert_eq!(state.status, TransferStatus::Evaluating);
620 assert_eq!(state.progress_counts(), TransferProgressCounts::default());
621
622 assert_eq!(handle.id(), id);
623 assert_eq!(handle.status(), TransferStatus::Evaluating);
624 assert_eq!(handle.remaining_blocks(), blocks);
625 }
626
627 #[test]
628 fn test_transfer_state_progress() {
629 let id = TransferId::new();
630 let blocks = vec![1, 2, 3, 4, 5];
631 let (mut state, handle) = TransferState::new(id, blocks);
632
633 state.add_passed(vec![1, 2, 3]);
635 state.add_filtered(vec![4, 5]);
636 assert_eq!(handle.passed_blocks(), vec![1, 2, 3]);
637
638 state.set_status(TransferStatus::Transferring);
640 state.mark_in_flight(vec![1, 2]);
641 assert_eq!(handle.status(), TransferStatus::Transferring);
642
643 state.mark_completed(vec![1]);
645 assert_eq!(handle.completed_blocks(), vec![1]);
646 assert_eq!(state.in_flight_count(), 1);
647
648 state.mark_completed(vec![2, 3]);
650 state.set_complete();
651
652 assert_eq!(handle.status(), TransferStatus::Complete);
653 assert_eq!(handle.completed_blocks(), vec![1, 2, 3]);
654 }
655
656 #[tokio::test]
657 async fn test_transfer_handle_wait() {
658 let id = TransferId::new();
659 let blocks = vec![1, 2, 3];
660 let (mut state, mut handle) = TransferState::new(id, blocks);
661
662 tokio::spawn(async move {
664 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
665 state.add_passed(vec![1, 2, 3]);
666 state.mark_completed(vec![1, 2, 3]);
667 state.set_complete();
668 });
669
670 let result = tokio::time::timeout(tokio::time::Duration::from_millis(100), handle.wait())
672 .await
673 .expect("Should complete within timeout")
674 .expect("Should succeed");
675
676 assert_eq!(result.status, TransferStatus::Complete);
677 assert_eq!(result.completed_blocks, vec![1, 2, 3]);
678 }
679
680 #[test]
681 fn test_mark_failed_removes_from_in_flight() {
682 let id = TransferId::new();
683 let blocks = vec![1, 2, 3];
684 let (mut state, handle) = TransferState::new(id, blocks);
685
686 state.add_passed(vec![1, 2, 3]);
687 state.mark_in_flight(vec![1, 2, 3]);
688 assert_eq!(state.in_flight_count(), 3);
689
690 state.mark_failed(vec![2]);
691 assert_eq!(state.in_flight_count(), 2);
692 assert_eq!(handle.failed_blocks(), vec![2]);
693 assert!(handle.completed_blocks().is_empty());
694 }
695
696 #[test]
697 fn test_mark_failed_updates_remaining() {
698 let id = TransferId::new();
699 let blocks = vec![1, 2, 3];
700 let (mut state, handle) = TransferState::new(id, blocks);
701
702 state.add_passed(vec![1, 2, 3]);
703 state.mark_in_flight(vec![1, 2, 3]);
704
705 state.mark_failed(vec![2]);
707 let remaining = handle.remaining_blocks();
708 assert!(remaining.contains(&1));
709 assert!(!remaining.contains(&2));
710 assert!(remaining.contains(&3));
711 }
712
713 #[test]
714 fn test_partial_failure_result() {
715 let id = TransferId::new();
716 let blocks = vec![1, 2, 3, 4, 5];
717 let (mut state, handle) = TransferState::new(id, blocks);
718
719 state.add_passed(vec![1, 2, 3]);
720 state.add_filtered(vec![4, 5]);
721 state.mark_in_flight(vec![1, 2, 3]);
722
723 state.mark_completed(vec![1, 3]);
725 state.mark_failed(vec![2]);
726
727 assert_eq!(handle.completed_blocks(), vec![1, 3]);
728 assert_eq!(handle.failed_blocks(), vec![2]);
729 assert_eq!(state.in_flight_count(), 0);
730
731 let progress = state.progress_counts();
733 let total = progress.passed + state.filtered_out.len();
734 let done = progress.settled() + state.filtered_out.len();
735 assert_eq!(done, total);
736
737 let failed_count = progress.failed;
739 assert!(failed_count > 0);
740 state.set_error(format!(
741 "{failed_count} blocks failed to transfer to object storage",
742 ));
743 assert_eq!(state.status, TransferStatus::Failed);
744 }
745
746 #[test]
747 fn test_progress_cursor_consumes_each_block_once() {
748 let id = TransferId::new();
749 let (mut state, handle) = TransferState::new(id, vec![1, 2, 3]);
750 let mut cursor = handle.new_progress_cursor();
751
752 state.add_passed([1, 2, 3]);
753 state.mark_completed([1]);
754 let first = handle.consume_progress(&mut cursor);
755 assert_eq!(first.passed_blocks, vec![1, 2, 3]);
756 assert_eq!(first.completed_blocks, vec![1]);
757 assert!(first.failed_blocks.is_empty());
758
759 state.mark_completed([2]);
760 state.mark_failed([3]);
761 let second = handle.consume_progress(&mut cursor);
762 assert!(second.passed_blocks.is_empty());
763 assert_eq!(second.completed_blocks, vec![2]);
764 assert_eq!(second.failed_blocks, vec![3]);
765 assert!(handle.consume_progress(&mut cursor).is_empty());
766 assert_eq!(
767 handle.progress_counts(),
768 TransferProgressCounts {
769 passed: 3,
770 completed: 2,
771 failed: 1,
772 }
773 );
774 }
775
776 #[tokio::test]
777 async fn test_partial_failure_wait_result() {
778 let id = TransferId::new();
779 let blocks = vec![1, 2, 3];
780 let (mut state, mut handle) = TransferState::new(id, blocks);
781
782 tokio::spawn(async move {
783 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
784 state.add_passed(vec![1, 2, 3]);
785 state.mark_in_flight(vec![1, 2, 3]);
786 state.mark_completed(vec![1, 3]);
787 state.mark_failed(vec![2]);
788 state.set_error("1 blocks failed to transfer to object storage".to_string());
789 });
790
791 let result = tokio::time::timeout(tokio::time::Duration::from_millis(100), handle.wait())
792 .await
793 .expect("Should complete within timeout")
794 .expect("Should succeed");
795
796 assert_eq!(result.status, TransferStatus::Failed);
797 assert_eq!(result.completed_blocks, vec![1, 3]);
798 assert_eq!(result.failed_blocks, vec![2]);
799 assert!(result.error.is_some());
800 }
801}