1use std::collections::{BTreeMap, VecDeque};
19use std::sync::Arc;
20use std::sync::Mutex;
21use std::sync::atomic::{AtomicU64, Ordering};
22
23use tokio::sync::Notify;
24
25use crate::storage::{OPEN_END, RangeRecord, RangeState};
26
27pub const MIN_CHUNK: u64 = 4 << 20;
30pub const MAX_CHUNK: u64 = 128 << 20;
33pub const SPLIT_MARGIN: u64 = 4 << 20;
37pub const MIN_SPLIT_TAIL: u64 = 8 << 20;
39
40pub fn chunk_size(total: u64, connections: usize) -> u64 {
58 let target_chunks = (connections as u64).max(1);
59 (total / target_chunks).clamp(MIN_CHUNK, MAX_CHUNK)
60}
61
62pub fn plan(total: u64, connections: usize) -> Vec<RangeRecord> {
64 if total == 0 {
65 return Vec::new();
66 }
67 let chunk = chunk_size(total, connections);
68 let mut ranges = Vec::new();
69 let mut start = 0u64;
70 let mut idx = 0u64;
71 while start < total {
72 let end = (start + chunk - 1).min(total - 1);
73 ranges.push(RangeRecord {
74 idx,
75 start,
76 end,
77 state: RangeState::Pending,
78 bytes_written: 0,
79 });
80 start = end + 1;
81 idx += 1;
82 }
83 ranges
84}
85
86pub fn plan_primed(total: u64, connections: usize, primed: u64) -> Vec<RangeRecord> {
98 if total == 0 || primed == 0 || primed >= total {
100 return plan(total, connections);
101 }
102
103 let mut ranges = vec![RangeRecord {
104 idx: 0,
105 start: 0,
106 end: primed - 1,
107 state: RangeState::Pending,
108 bytes_written: 0,
109 }];
110
111 let rest = total - primed;
112 let chunk = chunk_size(rest, connections.saturating_sub(1).max(1));
113 let mut start = primed;
114 let mut idx = 1u64;
115 while start < total {
116 let end = (start + chunk - 1).min(total - 1);
117 ranges.push(RangeRecord {
118 idx,
119 start,
120 end,
121 state: RangeState::Pending,
122 bytes_written: 0,
123 });
124 start = end + 1;
125 idx += 1;
126 }
127 ranges
128}
129
130pub fn plan_sequential(total: Option<u64>) -> Vec<RangeRecord> {
133 vec![RangeRecord {
134 idx: 0,
135 start: 0,
136 end: total.map(|t| t.saturating_sub(1)).unwrap_or(OPEN_END),
137 state: RangeState::Pending,
138 bytes_written: 0,
139 }]
140}
141
142#[derive(Clone)]
144pub struct Lease {
145 pub idx: u64,
146 pub start: u64,
147 pub end: Arc<AtomicU64>,
150 pub progress: Arc<AtomicU64>,
152}
153
154impl Lease {
155 pub fn end(&self) -> u64 {
156 self.end.load(Ordering::Acquire)
157 }
158
159 pub fn progress(&self) -> u64 {
160 self.progress.load(Ordering::Acquire)
161 }
162
163 pub fn cursor(&self) -> u64 {
165 self.start + self.progress()
166 }
167
168 pub fn is_open_ended(&self) -> bool {
169 self.end() >= OPEN_END
170 }
171
172 pub fn remaining(&self) -> u64 {
174 let end = self.end();
175 let cursor = self.cursor();
176 if cursor > end { 0 } else { end - cursor + 1 }
177 }
178
179 pub fn publish_progress(&self, bytes_from_start: u64) {
180 self.progress.store(bytes_from_start, Ordering::Release);
181 }
182}
183
184struct Live {
185 start: u64,
186 end: Arc<AtomicU64>,
187 progress: Arc<AtomicU64>,
188 state: RangeState,
189 leased: bool,
190}
191
192struct State {
193 ranges: BTreeMap<u64, Live>,
194 pending: VecDeque<u64>,
195 next_idx: u64,
196}
197
198pub struct Scheduler {
199 state: Mutex<State>,
200 wake: Notify,
203}
204
205#[derive(Debug, Clone, Copy)]
207pub struct Split {
208 pub shrunk: RangeRecord,
209 pub added: RangeRecord,
210}
211
212impl Scheduler {
213 pub fn from_ranges(ranges: &[RangeRecord]) -> Self {
217 let mut map = BTreeMap::new();
218 let mut pending = VecDeque::new();
219 let mut next_idx = 0;
220 for r in ranges {
221 next_idx = next_idx.max(r.idx + 1);
222 let complete = r.state == RangeState::Complete;
223 map.insert(
224 r.idx,
225 Live {
226 start: r.start,
227 end: Arc::new(AtomicU64::new(r.end)),
228 progress: Arc::new(AtomicU64::new(if complete {
229 r.size()
230 } else {
231 r.bytes_written
232 })),
233 state: if complete {
234 RangeState::Complete
235 } else {
236 RangeState::Pending
237 },
238 leased: false,
239 },
240 );
241 if !complete {
242 pending.push_back(r.idx);
243 }
244 }
245 Self {
246 state: Mutex::new(State {
247 ranges: map,
248 pending,
249 next_idx,
250 }),
251 wake: Notify::new(),
252 }
253 }
254
255 fn lock(&self) -> std::sync::MutexGuard<'_, State> {
256 self.state.lock().unwrap_or_else(|e| e.into_inner())
257 }
258
259 pub fn acquire(&self) -> Option<(Lease, Option<Split>)> {
262 let mut st = self.lock();
263 while let Some(idx) = st.pending.pop_front() {
264 let Some(live) = st.ranges.get_mut(&idx) else {
265 continue;
266 };
267 if live.state == RangeState::Complete || live.leased {
268 continue;
269 }
270 live.leased = true;
271 live.state = RangeState::Downloading;
272 return Some((
273 Lease {
274 idx,
275 start: live.start,
276 end: live.end.clone(),
277 progress: live.progress.clone(),
278 },
279 None,
280 ));
281 }
282 self.split_locked(&mut st).map(|(l, s)| (l, Some(s)))
283 }
284
285 fn split_locked(&self, st: &mut State) -> Option<(Lease, Split)> {
288 let mut best: Option<(u64, u64)> = None; for (idx, live) in st.ranges.iter() {
290 if !live.leased || live.state == RangeState::Complete {
291 continue;
292 }
293 let end = live.end.load(Ordering::Acquire);
294 if end >= OPEN_END {
295 continue;
297 }
298 let cursor = live.start + live.progress.load(Ordering::Acquire);
299 let split_at = cursor + SPLIT_MARGIN;
300 if end < split_at {
301 continue;
302 }
303 let tail = end - split_at + 1;
304 if tail < MIN_SPLIT_TAIL {
305 continue;
306 }
307 if best.is_none_or(|(_, best_tail)| tail > best_tail) {
308 best = Some((*idx, tail));
309 }
310 }
311
312 let (victim_idx, _) = best?;
313 let (victim_start, old_end, cursor) = {
314 let live = st.ranges.get(&victim_idx)?;
315 (
316 live.start,
317 live.end.load(Ordering::Acquire),
318 live.start + live.progress.load(Ordering::Acquire),
319 )
320 };
321
322 let midpoint = cursor + (old_end - cursor) / 2;
325 let split_at = midpoint.max(cursor + SPLIT_MARGIN);
326 if split_at > old_end || old_end - split_at + 1 < MIN_SPLIT_TAIL {
327 return None;
328 }
329
330 let new_victim_end = split_at - 1;
333 let victim_progress = {
334 let live = st.ranges.get_mut(&victim_idx)?;
335 live.end.store(new_victim_end, Ordering::Release);
336 live.progress.load(Ordering::Acquire)
337 };
338
339 let new_idx = st.next_idx;
340 st.next_idx += 1;
341 let end = Arc::new(AtomicU64::new(old_end));
342 let progress = Arc::new(AtomicU64::new(0));
343 st.ranges.insert(
344 new_idx,
345 Live {
346 start: split_at,
347 end: end.clone(),
348 progress: progress.clone(),
349 state: RangeState::Downloading,
350 leased: true,
351 },
352 );
353
354 Some((
355 Lease {
356 idx: new_idx,
357 start: split_at,
358 end,
359 progress,
360 },
361 Split {
362 shrunk: RangeRecord {
363 idx: victim_idx,
364 start: victim_start,
365 end: new_victim_end,
366 state: RangeState::Downloading,
367 bytes_written: victim_progress,
368 },
369 added: RangeRecord {
370 idx: new_idx,
371 start: split_at,
372 end: old_end,
373 state: RangeState::Pending,
374 bytes_written: 0,
375 },
376 },
377 ))
378 }
379
380 pub fn complete(&self, idx: u64) {
382 let mut st = self.lock();
383 if let Some(live) = st.ranges.get_mut(&idx) {
384 live.state = RangeState::Complete;
385 live.leased = false;
386 let end = live.end.load(Ordering::Acquire);
387 live.progress.store(
388 end.saturating_sub(live.start).saturating_add(1),
389 Ordering::Release,
390 );
391 }
392 drop(st);
393 self.wake.notify_waiters();
394 }
395
396 pub fn release(&self, idx: u64) {
400 let mut st = self.lock();
401 if let Some(live) = st.ranges.get_mut(&idx) {
402 if live.state != RangeState::Complete {
403 live.state = RangeState::Pending;
404 live.leased = false;
405 st.pending.push_back(idx);
406 }
407 }
408 drop(st);
409 self.wake.notify_waiters();
410 }
411
412 pub fn fail(&self, idx: u64) {
415 let mut st = self.lock();
416 if let Some(live) = st.ranges.get_mut(&idx) {
417 live.state = RangeState::Failed;
418 live.leased = false;
419 }
420 drop(st);
421 self.wake.notify_waiters();
422 }
423
424 pub fn set_end(&self, idx: u64, end: u64) {
427 let st = self.lock();
428 if let Some(live) = st.ranges.get(&idx) {
429 live.end.store(end, Ordering::Release);
430 }
431 }
432
433 pub fn is_finished(&self) -> bool {
434 let st = self.lock();
435 st.ranges.values().all(|l| l.state == RangeState::Complete)
436 }
437
438 pub fn has_failure(&self) -> bool {
439 let st = self.lock();
440 st.ranges.values().any(|l| l.state == RangeState::Failed)
441 }
442
443 pub fn is_drained(&self) -> bool {
446 let st = self.lock();
447 st.pending.is_empty()
448 && st
449 .ranges
450 .values()
451 .all(|l| matches!(l.state, RangeState::Complete | RangeState::Failed) || l.leased)
452 && !st.ranges.values().any(|l| l.leased)
453 }
454
455 pub async fn wait_for_change(&self, timeout: std::time::Duration) {
456 let _ = tokio::time::timeout(timeout, self.wake.notified()).await;
457 }
458
459 pub fn notify(&self) {
460 self.wake.notify_waiters();
461 }
462
463 pub fn snapshot(&self) -> Vec<RangeRecord> {
465 let st = self.lock();
466 st.ranges
467 .iter()
468 .map(|(idx, live)| RangeRecord {
469 idx: *idx,
470 start: live.start,
471 end: live.end.load(Ordering::Acquire),
472 state: live.state,
473 bytes_written: live.progress.load(Ordering::Acquire),
474 })
475 .collect()
476 }
477
478 pub fn counts(&self) -> (usize, usize) {
479 let st = self.lock();
480 let complete = st
481 .ranges
482 .values()
483 .filter(|l| l.state == RangeState::Complete)
484 .count();
485 (complete, st.ranges.len())
486 }
487
488 pub fn written_bytes(&self) -> u64 {
490 let st = self.lock();
491 st.ranges
492 .values()
493 .map(|l| l.progress.load(Ordering::Acquire))
494 .sum()
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 fn assert_partition(ranges: &[RangeRecord], total: u64) {
504 let mut sorted: Vec<_> = ranges.to_vec();
505 sorted.sort_by_key(|r| r.start);
506 let mut cursor = 0u64;
507 for r in &sorted {
508 assert_eq!(
509 r.start, cursor,
510 "gap or overlap at range {} ({}..={})",
511 r.idx, r.start, r.end
512 );
513 assert!(r.end >= r.start, "inverted range {}", r.idx);
514 assert!(r.end < total, "range {} extends past {total}", r.idx);
515 cursor = r.end + 1;
516 }
517 assert_eq!(cursor, total, "ranges do not cover the whole file");
518 }
519
520 #[test]
521 fn plan_partitions_the_file() {
522 for total in [1u64, 2, MIN_CHUNK - 1, MIN_CHUNK, MIN_CHUNK + 1, 1 << 30] {
523 for conns in [1usize, 4, 8, 32] {
524 let ranges = plan(total, conns);
525 assert!(!ranges.is_empty());
526 assert_partition(&ranges, total);
527 }
528 }
529 }
530
531 #[test]
532 fn plan_of_empty_file_is_empty() {
533 assert!(plan(0, 8).is_empty());
534 }
535
536 #[test]
537 fn chunk_size_is_bounded() {
538 assert_eq!(chunk_size(1, 8), MIN_CHUNK);
539 assert_eq!(chunk_size(u64::MAX, 8), MAX_CHUNK);
540 let c = chunk_size(10 << 30, 8);
541 assert!((MIN_CHUNK..=MAX_CHUNK).contains(&c));
542 }
543
544 #[test]
545 fn an_idle_worker_steals_from_the_range_furthest_behind() {
546 let total = 256 << 20;
552 let s = Scheduler::from_ranges(&plan(total, 4));
553 let mut leases = Vec::new();
554 for _ in 0..4 {
555 let (lease, split) = s.acquire().expect("a pending range");
556 assert!(split.is_none(), "no split while pending work remains");
557 leases.push(lease);
558 }
559
560 for lease in &leases[..3] {
562 s.complete(lease.idx);
563 }
564 let straggler = &leases[3];
565 straggler.publish_progress(1 << 20);
566
567 let (stolen, split) = s
568 .acquire()
569 .expect("an idle worker must be able to steal from the straggler");
570 let split = split.expect("the only work left is inside a leased range");
571 assert_eq!(split.shrunk.idx, straggler.idx);
572
573 assert!(
576 stolen.start > straggler.start + straggler.progress(),
577 "stole bytes the victim may still write"
578 );
579 assert!(stolen.end() < straggler.start + (total / 4));
580 assert_eq!(
581 straggler.end(),
582 stolen.start - 1,
583 "the victim's ceiling must drop to meet the stolen tail"
584 );
585 }
586
587 #[test]
588 fn primed_plan_pins_its_first_range_to_the_primed_bytes() {
589 let total = 64 << 20;
590 let primed = MIN_CHUNK;
591 let ranges = plan_primed(total, 4, primed);
592
593 assert_eq!(ranges[0].start, 0);
596 assert_eq!(ranges[0].end, primed - 1);
597
598 for pair in ranges.windows(2) {
600 assert_eq!(pair[1].start, pair[0].end + 1, "gap or overlap in the plan");
601 }
602 assert_eq!(ranges.last().unwrap().end, total - 1);
603 for (i, r) in ranges.iter().enumerate() {
604 assert_eq!(r.idx, i as u64);
605 }
606 }
607
608 #[test]
609 fn primed_plan_falls_back_when_there_is_no_boundary_to_pin() {
610 let total = 64 << 20;
611 assert_eq!(plan_primed(total, 4, 0), plan(total, 4));
613 assert_eq!(plan_primed(total, 4, total), plan(total, 4));
614 assert_eq!(plan_primed(total, 4, total + 1), plan(total, 4));
615 assert!(plan_primed(0, 4, MIN_CHUNK).is_empty());
616 }
617
618 #[test]
619 fn sequential_plan_handles_unknown_size() {
620 let r = plan_sequential(None);
621 assert_eq!(r.len(), 1);
622 assert!(r[0].is_open_ended());
623
624 let r = plan_sequential(Some(1000));
625 assert_eq!(r[0].end, 999);
626 }
627
628 #[test]
629 fn acquire_hands_out_each_range_once() {
630 let total = 100 << 20;
631 let s = Scheduler::from_ranges(&plan(total, 4));
632 let (_, count) = s.counts();
633 let mut seen = Vec::new();
634 for _ in 0..count {
635 let (lease, split) = s.acquire().expect("a pending range");
636 assert!(split.is_none(), "should not split while ranges are pending");
637 assert!(
638 !seen.contains(&lease.idx),
639 "range {} leased twice",
640 lease.idx
641 );
642 seen.push(lease.idx);
643 }
644 assert_eq!(seen.len(), count);
645
646 if let Some((lease, split)) = s.acquire() {
650 assert!(
651 split.is_some(),
652 "a lease beyond the plan must come from a split"
653 );
654 assert!(
655 !seen.contains(&lease.idx),
656 "split handed back an existing range"
657 );
658 }
659 }
660
661 #[test]
662 fn resumes_only_incomplete_ranges() {
663 let mut ranges = plan(100 << 20, 4);
664 ranges[0].state = RangeState::Complete;
665 ranges[0].bytes_written = ranges[0].size();
666 ranges[1].state = RangeState::Downloading;
668 ranges[1].bytes_written = 1024;
669
670 let s = Scheduler::from_ranges(&ranges);
671 let mut leases = Vec::new();
672 while let Some((lease, _)) = s.acquire() {
673 leases.push(lease);
674 }
675 assert!(
676 !leases.iter().any(|l| l.idx == 0),
677 "completed range must not be re-leased"
678 );
679 let resumed = leases
680 .iter()
681 .find(|l| l.idx == 1)
682 .expect("range 1 re-leased");
683 assert_eq!(resumed.cursor(), ranges[1].start + 1024);
684 assert_eq!(s.written_bytes(), ranges[0].size() + 1024);
685 }
686
687 #[test]
688 fn split_never_overlaps_and_preserves_the_partition() {
689 let total = 512 << 20;
690 let s = Scheduler::from_ranges(&plan_sequential(Some(total)));
691 let (victim, _) = s.acquire().expect("one range to lease");
692
693 victim.publish_progress(16 << 20);
695 let (thief, split) = s.acquire().expect("split should produce work");
696 let split = split.expect("expected a split record");
697
698 assert!(
699 thief.start > victim.start + victim.progress() + SPLIT_MARGIN - 1,
700 "split point {} too close to victim cursor {}",
701 thief.start,
702 victim.cursor()
703 );
704 assert_eq!(victim.end() + 1, thief.start, "split left a gap");
705 assert_eq!(thief.end(), total - 1);
706 assert_eq!(split.shrunk.end + 1, split.added.start);
707
708 assert_partition(&s.snapshot(), total);
709 }
710
711 #[test]
712 fn split_refuses_when_the_tail_is_small() {
713 let s = Scheduler::from_ranges(&plan_sequential(Some(8 << 20)));
715 let (lease, _) = s.acquire().unwrap();
716 lease.publish_progress(1 << 20);
717 assert!(s.acquire().is_none(), "should not split a tiny tail");
718 }
719
720 #[test]
721 fn split_refuses_open_ended_ranges() {
722 let s = Scheduler::from_ranges(&plan_sequential(None));
723 let (lease, _) = s.acquire().unwrap();
724 lease.publish_progress(64 << 20);
725 assert!(s.acquire().is_none(), "cannot split an unknown length");
726 }
727
728 #[test]
729 fn repeated_splits_keep_the_partition_intact() {
730 let total = 4u64 << 30;
731 let s = Scheduler::from_ranges(&plan(total, 2));
732 let mut leases = Vec::new();
733 while let Some((lease, _)) = s.acquire() {
734 leases.push(lease);
735 }
736 for round in 0..6 {
738 for l in &leases {
739 l.publish_progress((round + 1) * (8 << 20));
740 }
741 if let Some((lease, _)) = s.acquire() {
742 leases.push(lease);
743 }
744 assert_partition(&s.snapshot(), total);
745 }
746 }
747
748 #[test]
749 fn release_requeues_with_progress_kept() {
750 let s = Scheduler::from_ranges(&plan(100 << 20, 2));
751 let (lease, _) = s.acquire().unwrap();
752 lease.publish_progress(4096);
753 s.release(lease.idx);
754
755 let mut found = None;
756 while let Some((l, _)) = s.acquire() {
757 if l.idx == lease.idx {
758 found = Some(l);
759 break;
760 }
761 }
762 let again = found.expect("released range should be handed out again");
763 assert_eq!(again.progress(), 4096);
764 assert!(!s.is_finished());
765 }
766
767 #[test]
768 fn completion_is_tracked() {
769 let ranges = plan(100 << 20, 2);
770 let s = Scheduler::from_ranges(&ranges);
771 let mut leases = Vec::new();
772 while let Some((l, _)) = s.acquire() {
773 leases.push(l);
774 }
775 for l in &leases {
776 s.complete(l.idx);
777 }
778 assert!(s.is_finished());
779 let (done, total) = s.counts();
780 assert_eq!(done, total);
781 assert_eq!(s.written_bytes(), 100 << 20);
782 assert!(!s.has_failure());
783 }
784
785 #[test]
786 fn failure_is_isolated() {
787 let s = Scheduler::from_ranges(&plan(100 << 20, 2));
788 let (a, _) = s.acquire().unwrap();
789 let (b, _) = s.acquire().unwrap();
790 b.publish_progress(1000);
791 s.complete(b.idx);
792 s.fail(a.idx);
793
794 assert!(s.has_failure());
795 assert!(!s.is_finished());
796 let snap = s.snapshot();
798 let completed = snap.iter().find(|r| r.idx == b.idx).unwrap();
799 assert_eq!(completed.state, RangeState::Complete);
800 }
801
802 #[test]
803 fn lease_remaining_shrinks_with_the_ceiling() {
804 let s = Scheduler::from_ranges(&plan_sequential(Some(1000)));
805 let (lease, _) = s.acquire().unwrap();
806 assert_eq!(lease.remaining(), 1000);
807 lease.publish_progress(400);
808 assert_eq!(lease.remaining(), 600);
809 s.set_end(lease.idx, 499);
810 assert_eq!(lease.remaining(), 100);
811 lease.publish_progress(500);
812 assert_eq!(lease.remaining(), 0);
813 }
814}