1use std::collections::{BTreeMap, HashMap, VecDeque};
13
14pub type SpsStreamId = u64;
20
21#[inline]
26pub fn xorshift64(state: &mut u64) -> u64 {
27 let mut x = *state;
28 x ^= x << 13;
29 x ^= x >> 7;
30 x ^= x << 17;
31 *state = x;
32 x
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum SpsSchedulingPolicy {
42 StrictPriority,
44 WeightedFairQueuing,
46 DeficitRoundRobin,
48 EarliestDeadlineFirst,
50 HierarchicalToken,
52}
53
54#[derive(Debug, Clone)]
60pub struct SpsSchedulerConfig {
61 pub max_streams: usize,
63 pub quantum_bytes: u64,
65 pub strict_priority_threshold: u32,
68 pub deficit_rounds: u32,
70}
71
72impl Default for SpsSchedulerConfig {
73 fn default() -> Self {
74 Self {
75 max_streams: 1024,
76 quantum_bytes: 1500,
77 strict_priority_threshold: 240,
78 deficit_rounds: 1,
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
89pub struct SpsStream {
90 pub id: SpsStreamId,
92 pub priority: u32,
94 pub weight: u32,
96 pub pending_bytes: u64,
98 pub deficit_counter: i64,
100 pub send_count: u64,
102 pub bytes_sent: u64,
104 pub last_scheduled_ts: u64,
106 pub is_blocked: bool,
108 pub deadline: u64,
110 pub htb_tokens: i64,
112 pub htb_rate: u64,
114 pub htb_burst: u64,
116}
117
118impl SpsStream {
119 pub fn new(id: SpsStreamId, priority: u32, weight: u32) -> Self {
121 Self {
122 id,
123 priority,
124 weight: weight.max(1),
125 pending_bytes: 0,
126 deficit_counter: 0,
127 send_count: 0,
128 bytes_sent: 0,
129 last_scheduled_ts: 0,
130 is_blocked: false,
131 deadline: u64::MAX,
132 htb_tokens: 0,
133 htb_rate: 1500,
134 htb_burst: 65535,
135 }
136 }
137
138 #[inline]
140 pub fn is_eligible(&self) -> bool {
141 !self.is_blocked && self.pending_bytes > 0
142 }
143}
144
145#[derive(Debug, Clone, Default)]
151pub struct SpsSchedulerStats {
152 pub total_scheduled: u64,
154 pub total_bytes: u64,
156 pub priority_distribution: HashMap<u32, u64>,
158 pub avg_wait: f64,
160 pub active_streams: usize,
162 pub blocked_streams: usize,
164 pub streams_removed: u64,
166 pub fairness_index: f64,
168}
169
170#[derive(Debug, Clone)]
176struct DrrEntry {
177 stream_id: SpsStreamId,
178 priority: u32,
179}
180
181pub struct StreamPriorityScheduler {
207 streams: HashMap<SpsStreamId, SpsStream>,
209 priority_queues: BTreeMap<u32, VecDeque<SpsStreamId>>,
211 config: SpsSchedulerConfig,
213 stats: SpsSchedulerStats,
215 tick: u64,
217 drr_active: VecDeque<DrrEntry>,
219 rng_state: u64,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
229pub enum SpsError {
230 #[error("stream {0} not found")]
231 StreamNotFound(SpsStreamId),
232 #[error("maximum stream limit ({0}) reached")]
233 MaxStreamsReached(usize),
234 #[error("stream {0} already registered")]
235 DuplicateStream(SpsStreamId),
236 #[error("weight must be >= 1")]
237 InvalidWeight,
238 #[error("batch size must be > 0")]
239 InvalidBatchSize,
240}
241
242impl StreamPriorityScheduler {
247 pub fn new(config: SpsSchedulerConfig) -> Self {
249 Self {
250 streams: HashMap::new(),
251 priority_queues: BTreeMap::new(),
252 config,
253 stats: SpsSchedulerStats::default(),
254 tick: 0,
255 drr_active: VecDeque::new(),
256 rng_state: 0xdeadbeef_cafebabe,
257 }
258 }
259
260 pub fn add_stream(
270 &mut self,
271 id: SpsStreamId,
272 priority: u32,
273 weight: u32,
274 ) -> Result<(), SpsError> {
275 if self.streams.len() >= self.config.max_streams {
276 return Err(SpsError::MaxStreamsReached(self.config.max_streams));
277 }
278 if self.streams.contains_key(&id) {
279 return Err(SpsError::DuplicateStream(id));
280 }
281 if weight == 0 {
282 return Err(SpsError::InvalidWeight);
283 }
284 let stream = SpsStream::new(id, priority, weight);
285 self.streams.insert(id, stream);
286 self.stats.active_streams = self.streams.len();
287 Ok(())
288 }
289
290 pub fn remove_stream(&mut self, id: SpsStreamId) -> Result<SpsStream, SpsError> {
295 let stream = self
296 .streams
297 .remove(&id)
298 .ok_or(SpsError::StreamNotFound(id))?;
299
300 for queue in self.priority_queues.values_mut() {
302 queue.retain(|&sid| sid != id);
303 }
304 self.drr_active.retain(|e| e.stream_id != id);
306
307 self.priority_queues.retain(|_, q| !q.is_empty());
309
310 self.stats.active_streams = self.streams.len();
311 self.stats.streams_removed += 1;
312 Ok(stream)
313 }
314
315 pub fn block_stream(&mut self, id: SpsStreamId) -> Result<(), SpsError> {
320 let stream = self
321 .streams
322 .get_mut(&id)
323 .ok_or(SpsError::StreamNotFound(id))?;
324 stream.is_blocked = true;
325 self.stats.blocked_streams = self.streams.values().filter(|s| s.is_blocked).count();
326 Ok(())
327 }
328
329 pub fn unblock_stream(&mut self, id: SpsStreamId) -> Result<(), SpsError> {
334 let stream = self
335 .streams
336 .get_mut(&id)
337 .ok_or(SpsError::StreamNotFound(id))?;
338 stream.is_blocked = false;
339 self.stats.blocked_streams = self.streams.values().filter(|s| s.is_blocked).count();
340 Ok(())
341 }
342
343 pub fn enqueue_bytes(&mut self, stream_id: SpsStreamId, bytes: u64) -> Result<(), SpsError> {
355 let stream = self
356 .streams
357 .get_mut(&stream_id)
358 .ok_or(SpsError::StreamNotFound(stream_id))?;
359
360 let was_empty = stream.pending_bytes == 0;
361 stream.pending_bytes = stream.pending_bytes.saturating_add(bytes);
362
363 if was_empty && !stream.is_blocked {
364 let priority = stream.priority;
365 self.priority_queues
366 .entry(priority)
367 .or_default()
368 .push_back(stream_id);
369 }
370 Ok(())
371 }
372
373 pub fn schedule_next(&mut self, policy: &SpsSchedulingPolicy) -> Option<(SpsStreamId, u64)> {
385 self.tick += 1;
386 match policy {
387 SpsSchedulingPolicy::StrictPriority => self.schedule_strict(),
388 SpsSchedulingPolicy::WeightedFairQueuing => self.schedule_wfq(),
389 SpsSchedulingPolicy::DeficitRoundRobin => self.schedule_drr_single(),
390 SpsSchedulingPolicy::EarliestDeadlineFirst => self.schedule_edf(),
391 SpsSchedulingPolicy::HierarchicalToken => self.schedule_htb(),
392 }
393 }
394
395 pub fn schedule_batch(
402 &mut self,
403 policy: &SpsSchedulingPolicy,
404 n: usize,
405 ) -> Vec<(SpsStreamId, u64)> {
406 let mut results = Vec::with_capacity(n);
407 for _ in 0..n {
408 match self.schedule_next(policy) {
409 Some(item) => results.push(item),
410 None => break,
411 }
412 }
413 results
414 }
415
416 pub fn run_drr_round(&mut self) -> Vec<(SpsStreamId, u64)> {
428 let rounds = self.config.deficit_rounds as usize;
429 let quantum = self.config.quantum_bytes;
430 let mut results = Vec::new();
431
432 for _ in 0..rounds {
433 if self.drr_active.is_empty() {
435 for stream in self.streams.values() {
436 if stream.is_eligible() {
437 self.drr_active.push_back(DrrEntry {
438 stream_id: stream.id,
439 priority: stream.priority,
440 });
441 }
442 }
443 }
444
445 let mut processed = VecDeque::new();
446 let len = self.drr_active.len();
447
448 for _ in 0..len {
449 let entry = match self.drr_active.pop_front() {
450 Some(e) => e,
451 None => break,
452 };
453
454 let stream = match self.streams.get_mut(&entry.stream_id) {
455 Some(s) => s,
456 None => continue,
457 };
458
459 if !stream.is_eligible() {
460 continue;
462 }
463
464 stream.deficit_counter += quantum as i64;
466
467 while stream.pending_bytes > 0 && stream.deficit_counter > 0 {
469 let send = stream.pending_bytes.min(stream.deficit_counter as u64);
470 stream.deficit_counter -= send as i64;
471 stream.pending_bytes -= send;
472 stream.bytes_sent += send;
473 stream.send_count += 1;
474 stream.last_scheduled_ts = self.tick;
475 self.tick += 1;
476 self.stats.total_scheduled += 1;
477 self.stats.total_bytes += send;
478 *self
479 .stats
480 .priority_distribution
481 .entry(stream.priority)
482 .or_insert(0) += 1;
483 results.push((entry.stream_id, send));
484 }
485
486 if stream.pending_bytes > 0 {
488 processed.push_back(DrrEntry {
489 stream_id: entry.stream_id,
490 priority: entry.priority,
491 });
492 } else {
493 stream.deficit_counter = 0;
495 }
496 }
497
498 self.drr_active = processed;
499 }
500
501 results
502 }
503
504 pub fn compute_fairness(&self) -> f64 {
513 let streams: Vec<f64> = self.streams.values().map(|s| s.bytes_sent as f64).collect();
514
515 let n = streams.len();
516 if n < 2 {
517 return 1.0;
518 }
519
520 let sum: f64 = streams.iter().sum();
521 let sum_sq: f64 = streams.iter().map(|x| x * x).sum();
522
523 if sum_sq == 0.0 {
524 return 1.0;
525 }
526
527 (sum * sum) / (n as f64 * sum_sq)
528 }
529
530 pub fn scheduler_stats(&mut self) -> SpsSchedulerStats {
536 self.stats.active_streams = self.streams.len();
537 self.stats.blocked_streams = self.streams.values().filter(|s| s.is_blocked).count();
538 self.stats.fairness_index = self.compute_fairness();
539 let scheduled: Vec<u64> = self
542 .streams
543 .values()
544 .filter(|s| s.send_count > 0)
545 .map(|s| self.tick.saturating_sub(s.last_scheduled_ts))
546 .collect();
547 if scheduled.is_empty() {
548 self.stats.avg_wait = 0.0;
549 } else {
550 self.stats.avg_wait = scheduled.iter().sum::<u64>() as f64 / scheduled.len() as f64;
551 }
552 self.stats.clone()
553 }
554
555 pub fn get_stream(&self, id: SpsStreamId) -> Option<&SpsStream> {
561 self.streams.get(&id)
562 }
563
564 pub fn get_stream_mut(&mut self, id: SpsStreamId) -> Option<&mut SpsStream> {
566 self.streams.get_mut(&id)
567 }
568
569 pub fn set_deadline(&mut self, id: SpsStreamId, deadline: u64) -> Result<(), SpsError> {
573 let stream = self
574 .streams
575 .get_mut(&id)
576 .ok_or(SpsError::StreamNotFound(id))?;
577 stream.deadline = deadline;
578 Ok(())
579 }
580
581 pub fn set_htb_params(
583 &mut self,
584 id: SpsStreamId,
585 rate: u64,
586 burst: u64,
587 ) -> Result<(), SpsError> {
588 let stream = self
589 .streams
590 .get_mut(&id)
591 .ok_or(SpsError::StreamNotFound(id))?;
592 stream.htb_rate = rate.max(1);
593 stream.htb_burst = burst.max(1);
594 Ok(())
595 }
596
597 pub fn htb_refill(&mut self, ticks: u64) {
599 for stream in self.streams.values_mut() {
600 let refill = (stream.htb_rate * ticks) as i64;
601 stream.htb_tokens = (stream.htb_tokens + refill).min(stream.htb_burst as i64);
602 }
603 }
604
605 pub fn tick(&self) -> u64 {
607 self.tick
608 }
609
610 pub fn stream_count(&self) -> usize {
612 self.streams.len()
613 }
614
615 pub fn eligible_count(&self) -> usize {
617 self.streams.values().filter(|s| s.is_eligible()).count()
618 }
619
620 pub fn drain_stream(&mut self, id: SpsStreamId) -> Result<u64, SpsError> {
624 let stream = self
625 .streams
626 .get_mut(&id)
627 .ok_or(SpsError::StreamNotFound(id))?;
628 let drained = stream.pending_bytes;
629 stream.pending_bytes = 0;
630 stream.deficit_counter = 0;
631 if let Some(queue) = self.priority_queues.get_mut(&stream.priority) {
633 queue.retain(|&sid| sid != id);
634 }
635 self.drr_active.retain(|e| e.stream_id != id);
636 Ok(drained)
637 }
638
639 pub fn update_priority(&mut self, id: SpsStreamId, new_priority: u32) -> Result<(), SpsError> {
643 let stream = self
644 .streams
645 .get_mut(&id)
646 .ok_or(SpsError::StreamNotFound(id))?;
647 let old_priority = stream.priority;
648 let has_data = stream.pending_bytes > 0 && !stream.is_blocked;
649 stream.priority = new_priority;
650
651 if has_data && old_priority != new_priority {
652 if let Some(q) = self.priority_queues.get_mut(&old_priority) {
654 q.retain(|&sid| sid != id);
655 }
656 self.priority_queues
658 .entry(new_priority)
659 .or_default()
660 .push_back(id);
661 }
662 Ok(())
663 }
664
665 fn schedule_strict(&mut self) -> Option<(SpsStreamId, u64)> {
674 let quantum = self.config.quantum_bytes;
675
676 let _jitter = xorshift64(&mut self.rng_state);
678
679 let chosen = self.priority_queues.iter().rev().find_map(|(_, queue)| {
681 queue
682 .iter()
683 .find(|&&sid| {
684 self.streams
685 .get(&sid)
686 .map(|s| s.is_eligible())
687 .unwrap_or(false)
688 })
689 .copied()
690 });
691
692 let id = chosen?;
693 self.dispatch_bytes(id, quantum)
694 }
695
696 fn schedule_wfq(&mut self) -> Option<(SpsStreamId, u64)> {
699 let quantum = self.config.quantum_bytes;
700 let strict_threshold = self.config.strict_priority_threshold;
701
702 let strict_id = self.priority_queues.iter().rev().find_map(|(&pri, queue)| {
704 if pri < strict_threshold {
705 return None;
706 }
707 queue
708 .iter()
709 .find(|&&sid| {
710 self.streams
711 .get(&sid)
712 .map(|s| s.is_eligible())
713 .unwrap_or(false)
714 })
715 .copied()
716 });
717
718 if let Some(id) = strict_id {
719 return self.dispatch_bytes(id, quantum);
720 }
721
722 let best = self
725 .streams
726 .values()
727 .filter(|s| s.is_eligible() && s.priority < strict_threshold)
728 .min_by(|a, b| {
729 let va = if a.weight == 0 {
730 f64::MAX
731 } else {
732 a.bytes_sent as f64 / a.weight as f64
733 };
734 let vb = if b.weight == 0 {
735 f64::MAX
736 } else {
737 b.bytes_sent as f64 / b.weight as f64
738 };
739 va.partial_cmp(&vb).unwrap_or(std::cmp::Ordering::Equal)
740 })
741 .map(|s| s.id);
742
743 let id = best?;
744 self.dispatch_bytes(id, quantum)
745 }
746
747 fn schedule_drr_single(&mut self) -> Option<(SpsStreamId, u64)> {
749 let quantum = self.config.quantum_bytes;
750
751 if self.drr_active.is_empty() {
753 for stream in self.streams.values() {
754 if stream.is_eligible() {
755 self.drr_active.push_back(DrrEntry {
756 stream_id: stream.id,
757 priority: stream.priority,
758 });
759 }
760 }
761 }
762
763 let list_len = self.drr_active.len();
765 for _ in 0..list_len {
766 let entry = self.drr_active.pop_front()?;
767 let stream = match self.streams.get_mut(&entry.stream_id) {
768 Some(s) => s,
769 None => continue,
770 };
771
772 if !stream.is_eligible() {
773 continue;
774 }
775
776 stream.deficit_counter += quantum as i64;
778
779 if stream.deficit_counter > 0 {
780 let send = stream.pending_bytes.min(stream.deficit_counter as u64);
781 stream.deficit_counter -= send as i64;
782 stream.pending_bytes -= send;
783 stream.bytes_sent += send;
784 stream.send_count += 1;
785 stream.last_scheduled_ts = self.tick;
786 self.stats.total_scheduled += 1;
787 self.stats.total_bytes += send;
788 *self
789 .stats
790 .priority_distribution
791 .entry(stream.priority)
792 .or_insert(0) += 1;
793
794 if stream.pending_bytes > 0 {
795 self.drr_active.push_back(DrrEntry {
796 stream_id: entry.stream_id,
797 priority: entry.priority,
798 });
799 } else {
800 stream.deficit_counter = 0;
801 }
802 return Some((entry.stream_id, send));
803 } else {
804 self.drr_active.push_back(entry);
806 }
807 }
808 None
809 }
810
811 fn schedule_edf(&mut self) -> Option<(SpsStreamId, u64)> {
813 let quantum = self.config.quantum_bytes;
814
815 let best = self
816 .streams
817 .values()
818 .filter(|s| s.is_eligible())
819 .min_by_key(|s| s.deadline)
820 .map(|s| s.id);
821
822 let id = best?;
823 self.dispatch_bytes(id, quantum)
824 }
825
826 fn schedule_htb(&mut self) -> Option<(SpsStreamId, u64)> {
828 let quantum = self.config.quantum_bytes;
829
830 let tokenized = self
832 .streams
833 .values()
834 .filter(|s| s.is_eligible() && s.htb_tokens > 0)
835 .max_by_key(|s| s.priority)
836 .map(|s| s.id);
837
838 let id = if let Some(id) = tokenized {
839 id
840 } else {
841 self.streams
843 .values()
844 .filter(|s| s.is_eligible())
845 .max_by_key(|s| s.priority)
846 .map(|s| s.id)?
847 };
848
849 if let Some(stream) = self.streams.get_mut(&id) {
851 let send = stream.pending_bytes.min(quantum);
852 stream.htb_tokens -= send as i64;
853 }
855
856 self.dispatch_bytes(id, quantum)
857 }
858
859 fn dispatch_bytes(&mut self, id: SpsStreamId, max_bytes: u64) -> Option<(SpsStreamId, u64)> {
863 let stream = self.streams.get_mut(&id)?;
864 if stream.pending_bytes == 0 {
865 return None;
866 }
867 let send = stream.pending_bytes.min(max_bytes);
868 stream.pending_bytes -= send;
869 stream.bytes_sent += send;
870 stream.send_count += 1;
871 stream.last_scheduled_ts = self.tick;
872
873 let priority = stream.priority;
874 self.stats.total_scheduled += 1;
875 self.stats.total_bytes += send;
876 *self
877 .stats
878 .priority_distribution
879 .entry(priority)
880 .or_insert(0) += 1;
881
882 if stream.pending_bytes == 0 {
884 if let Some(q) = self.priority_queues.get_mut(&priority) {
885 q.retain(|&sid| sid != id);
886 }
887 }
888
889 Some((id, send))
890 }
891}
892
893#[cfg(test)]
898mod tests {
899 use super::*;
900
901 fn make_scheduler() -> StreamPriorityScheduler {
902 StreamPriorityScheduler::new(SpsSchedulerConfig::default())
903 }
904
905 #[test]
908 fn test_new_scheduler_is_empty() {
909 let s = make_scheduler();
910 assert_eq!(s.stream_count(), 0);
911 assert_eq!(s.eligible_count(), 0);
912 }
913
914 #[test]
915 fn test_default_config_values() {
916 let cfg = SpsSchedulerConfig::default();
917 assert_eq!(cfg.max_streams, 1024);
918 assert!(cfg.quantum_bytes > 0);
919 assert!(cfg.deficit_rounds >= 1);
920 }
921
922 #[test]
925 fn test_add_stream_basic() {
926 let mut s = make_scheduler();
927 s.add_stream(1, 10, 1)
928 .expect("test: add_stream should succeed");
929 assert_eq!(s.stream_count(), 1);
930 }
931
932 #[test]
933 fn test_add_stream_duplicate_errors() {
934 let mut s = make_scheduler();
935 s.add_stream(1, 10, 1)
936 .expect("test: add_stream should succeed");
937 assert!(matches!(
938 s.add_stream(1, 5, 1),
939 Err(SpsError::DuplicateStream(1))
940 ));
941 }
942
943 #[test]
944 fn test_add_stream_zero_weight_errors() {
945 let mut s = make_scheduler();
946 assert!(matches!(
947 s.add_stream(1, 10, 0),
948 Err(SpsError::InvalidWeight)
949 ));
950 }
951
952 #[test]
953 fn test_add_stream_max_limit() {
954 let cfg = SpsSchedulerConfig {
955 max_streams: 2,
956 ..Default::default()
957 };
958 let mut s = StreamPriorityScheduler::new(cfg);
959 s.add_stream(1, 10, 1)
960 .expect("test: add_stream 1 should succeed");
961 s.add_stream(2, 10, 1)
962 .expect("test: add_stream 2 should succeed");
963 assert!(matches!(
964 s.add_stream(3, 10, 1),
965 Err(SpsError::MaxStreamsReached(2))
966 ));
967 }
968
969 #[test]
972 fn test_remove_existing_stream() {
973 let mut s = make_scheduler();
974 s.add_stream(1, 10, 1)
975 .expect("test: add_stream should succeed");
976 let removed = s
977 .remove_stream(1)
978 .expect("test: remove_stream should succeed");
979 assert_eq!(removed.id, 1);
980 assert_eq!(s.stream_count(), 0);
981 }
982
983 #[test]
984 fn test_remove_nonexistent_stream_errors() {
985 let mut s = make_scheduler();
986 assert!(matches!(
987 s.remove_stream(99),
988 Err(SpsError::StreamNotFound(99))
989 ));
990 }
991
992 #[test]
993 fn test_remove_stream_cleans_priority_queue() {
994 let mut s = make_scheduler();
995 s.add_stream(1, 10, 1)
996 .expect("test: add_stream should succeed");
997 s.enqueue_bytes(1, 1000)
998 .expect("test: enqueue_bytes should succeed");
999 s.remove_stream(1)
1000 .expect("test: remove_stream should succeed");
1001 let result = s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1003 assert!(result.is_none());
1004 }
1005
1006 #[test]
1009 fn test_block_stream() {
1010 let mut s = make_scheduler();
1011 s.add_stream(1, 10, 1)
1012 .expect("test: add_stream should succeed");
1013 s.enqueue_bytes(1, 100)
1014 .expect("test: enqueue_bytes should succeed");
1015 s.block_stream(1)
1016 .expect("test: block_stream should succeed");
1017 assert!(
1018 s.get_stream(1)
1019 .expect("test: stream 1 should exist")
1020 .is_blocked
1021 );
1022 let result = s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1023 assert!(result.is_none());
1024 }
1025
1026 #[test]
1027 fn test_unblock_stream() {
1028 let mut s = make_scheduler();
1029 s.add_stream(1, 10, 1)
1030 .expect("test: add_stream should succeed");
1031 s.enqueue_bytes(1, 100)
1032 .expect("test: enqueue_bytes should succeed");
1033 s.block_stream(1)
1034 .expect("test: block_stream should succeed");
1035 s.unblock_stream(1)
1036 .expect("test: unblock_stream should succeed");
1037 assert!(
1038 !s.get_stream(1)
1039 .expect("test: stream 1 should exist")
1040 .is_blocked
1041 );
1042 s.enqueue_bytes(1, 0).unwrap_or_default(); }
1045
1046 #[test]
1047 fn test_block_nonexistent_errors() {
1048 let mut s = make_scheduler();
1049 assert!(matches!(
1050 s.block_stream(7),
1051 Err(SpsError::StreamNotFound(7))
1052 ));
1053 }
1054
1055 #[test]
1056 fn test_unblock_nonexistent_errors() {
1057 let mut s = make_scheduler();
1058 assert!(matches!(
1059 s.unblock_stream(7),
1060 Err(SpsError::StreamNotFound(7))
1061 ));
1062 }
1063
1064 #[test]
1067 fn test_enqueue_bytes_increases_pending() {
1068 let mut s = make_scheduler();
1069 s.add_stream(1, 10, 1)
1070 .expect("test: add_stream should succeed");
1071 s.enqueue_bytes(1, 512)
1072 .expect("test: enqueue_bytes should succeed");
1073 assert_eq!(
1074 s.get_stream(1)
1075 .expect("test: stream 1 should exist")
1076 .pending_bytes,
1077 512
1078 );
1079 }
1080
1081 #[test]
1082 fn test_enqueue_bytes_accumulates() {
1083 let mut s = make_scheduler();
1084 s.add_stream(1, 10, 1)
1085 .expect("test: add_stream should succeed");
1086 s.enqueue_bytes(1, 100)
1087 .expect("test: first enqueue_bytes should succeed");
1088 s.enqueue_bytes(1, 200)
1089 .expect("test: second enqueue_bytes should succeed");
1090 assert_eq!(
1091 s.get_stream(1)
1092 .expect("test: stream 1 should exist")
1093 .pending_bytes,
1094 300
1095 );
1096 }
1097
1098 #[test]
1099 fn test_enqueue_bytes_nonexistent_errors() {
1100 let mut s = make_scheduler();
1101 assert!(matches!(
1102 s.enqueue_bytes(42, 100),
1103 Err(SpsError::StreamNotFound(42))
1104 ));
1105 }
1106
1107 #[test]
1110 fn test_strict_priority_basic() {
1111 let mut s = make_scheduler();
1112 s.add_stream(1, 100, 1)
1113 .expect("test: add_stream 1 should succeed");
1114 s.add_stream(2, 50, 1)
1115 .expect("test: add_stream 2 should succeed");
1116 s.enqueue_bytes(1, 1500)
1117 .expect("test: enqueue_bytes for stream 1 should succeed");
1118 s.enqueue_bytes(2, 1500)
1119 .expect("test: enqueue_bytes for stream 2 should succeed");
1120 let result = s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1121 assert_eq!(result.map(|(id, _)| id), Some(1));
1122 }
1123
1124 #[test]
1125 fn test_strict_priority_selects_highest() {
1126 let mut s = make_scheduler();
1127 for pri in [10u32, 50, 200, 5] {
1128 s.add_stream(pri as u64, pri, 1)
1129 .expect("test: add_stream should succeed");
1130 s.enqueue_bytes(pri as u64, 3000)
1131 .expect("test: enqueue_bytes should succeed");
1132 }
1133 let (id, _) = s
1134 .schedule_next(&SpsSchedulingPolicy::StrictPriority)
1135 .expect("test: schedule_next should return Some");
1136 assert_eq!(id, 200);
1137 }
1138
1139 #[test]
1140 fn test_strict_priority_no_eligible_returns_none() {
1141 let mut s = make_scheduler();
1142 s.add_stream(1, 10, 1)
1143 .expect("test: add_stream should succeed");
1144 let result = s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1146 assert!(result.is_none());
1147 }
1148
1149 #[test]
1150 fn test_strict_priority_exhausts_stream() {
1151 let mut s = make_scheduler();
1152 s.add_stream(1, 10, 1)
1153 .expect("test: add_stream should succeed");
1154 s.enqueue_bytes(1, 100)
1155 .expect("test: enqueue_bytes should succeed");
1156 let (_, sent) = s
1158 .schedule_next(&SpsSchedulingPolicy::StrictPriority)
1159 .expect("test: schedule_next should return Some");
1160 assert_eq!(sent, 100);
1161 assert_eq!(
1162 s.get_stream(1)
1163 .expect("test: stream 1 should exist")
1164 .pending_bytes,
1165 0
1166 );
1167 }
1168
1169 #[test]
1172 fn test_wfq_basic_scheduling() {
1173 let mut s = make_scheduler();
1174 s.add_stream(1, 10, 10)
1175 .expect("test: add_stream 1 should succeed");
1176 s.add_stream(2, 10, 1)
1177 .expect("test: add_stream 2 should succeed");
1178 s.enqueue_bytes(1, 5000)
1179 .expect("test: enqueue_bytes for stream 1 should succeed");
1180 s.enqueue_bytes(2, 5000)
1181 .expect("test: enqueue_bytes for stream 2 should succeed");
1182 let r = s.schedule_next(&SpsSchedulingPolicy::WeightedFairQueuing);
1184 assert!(r.is_some());
1185 }
1186
1187 #[test]
1188 fn test_wfq_strict_threshold_override() {
1189 let cfg = SpsSchedulerConfig {
1190 strict_priority_threshold: 100,
1191 ..Default::default()
1192 };
1193 let mut s = StreamPriorityScheduler::new(cfg);
1194 s.add_stream(1, 200, 1)
1195 .expect("test: add_stream 1 should succeed"); s.add_stream(2, 50, 100)
1197 .expect("test: add_stream 2 should succeed"); s.enqueue_bytes(1, 3000)
1199 .expect("test: enqueue_bytes for stream 1 should succeed");
1200 s.enqueue_bytes(2, 3000)
1201 .expect("test: enqueue_bytes for stream 2 should succeed");
1202 let (id, _) = s
1203 .schedule_next(&SpsSchedulingPolicy::WeightedFairQueuing)
1204 .expect("test: schedule_next should return Some");
1205 assert_eq!(id, 1); }
1207
1208 #[test]
1209 fn test_wfq_proportional_allocation() {
1210 let mut s = make_scheduler();
1211 s.add_stream(1, 10, 2)
1212 .expect("test: add_stream 1 should succeed");
1213 s.add_stream(2, 10, 1)
1214 .expect("test: add_stream 2 should succeed");
1215 s.enqueue_bytes(1, 100_000)
1216 .expect("test: enqueue_bytes for stream 1 should succeed");
1217 s.enqueue_bytes(2, 100_000)
1218 .expect("test: enqueue_bytes for stream 2 should succeed");
1219 let batch = s.schedule_batch(&SpsSchedulingPolicy::WeightedFairQueuing, 1000);
1221 assert!(!batch.is_empty());
1222 }
1223
1224 #[test]
1227 fn test_drr_single_step() {
1228 let mut s = make_scheduler();
1229 s.add_stream(1, 10, 1)
1230 .expect("test: add_stream should succeed");
1231 s.enqueue_bytes(1, 3000)
1232 .expect("test: enqueue_bytes should succeed");
1233 let r = s.schedule_next(&SpsSchedulingPolicy::DeficitRoundRobin);
1234 assert!(r.is_some());
1235 }
1236
1237 #[test]
1238 fn test_drr_round_basic() {
1239 let mut s = make_scheduler();
1240 s.add_stream(1, 10, 1)
1241 .expect("test: add_stream 1 should succeed");
1242 s.add_stream(2, 10, 1)
1243 .expect("test: add_stream 2 should succeed");
1244 s.enqueue_bytes(1, 3000)
1245 .expect("test: enqueue_bytes for stream 1 should succeed");
1246 s.enqueue_bytes(2, 3000)
1247 .expect("test: enqueue_bytes for stream 2 should succeed");
1248 let results = s.run_drr_round();
1249 assert!(!results.is_empty());
1250 }
1251
1252 #[test]
1253 fn test_drr_multiple_rounds() {
1254 let cfg = SpsSchedulerConfig {
1255 quantum_bytes: 500,
1256 deficit_rounds: 3,
1257 ..Default::default()
1258 };
1259 let mut s = StreamPriorityScheduler::new(cfg);
1260 s.add_stream(1, 10, 1)
1261 .expect("test: add_stream 1 should succeed");
1262 s.add_stream(2, 10, 1)
1263 .expect("test: add_stream 2 should succeed");
1264 s.enqueue_bytes(1, 10000)
1265 .expect("test: enqueue_bytes for stream 1 should succeed");
1266 s.enqueue_bytes(2, 10000)
1267 .expect("test: enqueue_bytes for stream 2 should succeed");
1268 let results = s.run_drr_round();
1269 assert!(!results.is_empty());
1270 }
1271
1272 #[test]
1273 fn test_drr_drain_eventually() {
1274 let cfg = SpsSchedulerConfig {
1275 quantum_bytes: 100,
1276 deficit_rounds: 1,
1277 ..Default::default()
1278 };
1279 let mut s = StreamPriorityScheduler::new(cfg);
1280 s.add_stream(1, 10, 1)
1281 .expect("test: add_stream should succeed");
1282 s.enqueue_bytes(1, 300)
1283 .expect("test: enqueue_bytes should succeed");
1284
1285 let mut total = 0u64;
1286 for _ in 0..20 {
1287 for (_, b) in s.run_drr_round() {
1288 total += b;
1289 }
1290 }
1291 assert_eq!(total, 300);
1292 }
1293
1294 #[test]
1297 fn test_edf_selects_smallest_deadline() {
1298 let mut s = make_scheduler();
1299 s.add_stream(1, 10, 1)
1300 .expect("test: add_stream 1 should succeed");
1301 s.add_stream(2, 10, 1)
1302 .expect("test: add_stream 2 should succeed");
1303 s.set_deadline(1, 1000)
1304 .expect("test: set_deadline for stream 1 should succeed");
1305 s.set_deadline(2, 500)
1306 .expect("test: set_deadline for stream 2 should succeed");
1307 s.enqueue_bytes(1, 1500)
1308 .expect("test: enqueue_bytes for stream 1 should succeed");
1309 s.enqueue_bytes(2, 1500)
1310 .expect("test: enqueue_bytes for stream 2 should succeed");
1311 let (id, _) = s
1312 .schedule_next(&SpsSchedulingPolicy::EarliestDeadlineFirst)
1313 .expect("test: schedule_next should return Some");
1314 assert_eq!(id, 2); }
1316
1317 #[test]
1318 fn test_edf_default_deadline_is_max() {
1319 let mut s = make_scheduler();
1320 s.add_stream(1, 10, 1)
1321 .expect("test: add_stream should succeed");
1322 s.enqueue_bytes(1, 100)
1323 .expect("test: enqueue_bytes should succeed");
1324 let r = s.schedule_next(&SpsSchedulingPolicy::EarliestDeadlineFirst);
1325 assert!(r.is_some());
1326 }
1327
1328 #[test]
1329 fn test_set_deadline_nonexistent_errors() {
1330 let mut s = make_scheduler();
1331 assert!(matches!(
1332 s.set_deadline(99, 100),
1333 Err(SpsError::StreamNotFound(99))
1334 ));
1335 }
1336
1337 #[test]
1340 fn test_htb_requires_refill() {
1341 let mut s = make_scheduler();
1342 s.add_stream(1, 10, 1)
1343 .expect("test: add_stream should succeed");
1344 s.set_htb_params(1, 1000, 10000)
1345 .expect("test: set_htb_params should succeed");
1346 s.enqueue_bytes(1, 5000)
1347 .expect("test: enqueue_bytes should succeed");
1348 let r = s.schedule_next(&SpsSchedulingPolicy::HierarchicalToken);
1350 assert!(r.is_some());
1352 }
1353
1354 #[test]
1355 fn test_htb_with_refill() {
1356 let mut s = make_scheduler();
1357 s.add_stream(1, 10, 1)
1358 .expect("test: add_stream should succeed");
1359 s.set_htb_params(1, 1000, 10000)
1360 .expect("test: set_htb_params should succeed");
1361 s.htb_refill(5); s.enqueue_bytes(1, 5000)
1363 .expect("test: enqueue_bytes should succeed");
1364 let (id, _) = s
1365 .schedule_next(&SpsSchedulingPolicy::HierarchicalToken)
1366 .expect("test: schedule_next should return Some");
1367 assert_eq!(id, 1);
1368 }
1369
1370 #[test]
1371 fn test_set_htb_params_nonexistent_errors() {
1372 let mut s = make_scheduler();
1373 assert!(matches!(
1374 s.set_htb_params(99, 1000, 2000),
1375 Err(SpsError::StreamNotFound(99))
1376 ));
1377 }
1378
1379 #[test]
1382 fn test_schedule_batch_returns_up_to_n() {
1383 let mut s = make_scheduler();
1384 for i in 1u64..=5 {
1385 s.add_stream(i, 10, 1)
1386 .expect("test: add_stream should succeed");
1387 s.enqueue_bytes(i, 500)
1388 .expect("test: enqueue_bytes should succeed");
1389 }
1390 let batch = s.schedule_batch(&SpsSchedulingPolicy::StrictPriority, 3);
1391 assert!(batch.len() <= 3);
1392 }
1393
1394 #[test]
1395 fn test_schedule_batch_stops_when_empty() {
1396 let mut s = make_scheduler();
1397 s.add_stream(1, 10, 1)
1398 .expect("test: add_stream should succeed");
1399 s.enqueue_bytes(1, 100)
1400 .expect("test: enqueue_bytes should succeed");
1401 let batch = s.schedule_batch(&SpsSchedulingPolicy::StrictPriority, 100);
1402 assert!(!batch.is_empty());
1404 let total: u64 = batch.iter().map(|(_, b)| b).sum();
1405 assert_eq!(total, 100);
1406 }
1407
1408 #[test]
1409 fn test_schedule_batch_zero_n() {
1410 let mut s = make_scheduler();
1411 s.add_stream(1, 10, 1)
1412 .expect("test: add_stream should succeed");
1413 s.enqueue_bytes(1, 100)
1414 .expect("test: enqueue_bytes should succeed");
1415 let batch = s.schedule_batch(&SpsSchedulingPolicy::StrictPriority, 0);
1416 assert!(batch.is_empty());
1417 }
1418
1419 #[test]
1422 fn test_fairness_empty_scheduler() {
1423 let s = make_scheduler();
1424 assert!((s.compute_fairness() - 1.0).abs() < 1e-9);
1425 }
1426
1427 #[test]
1428 fn test_fairness_single_stream() {
1429 let mut s = make_scheduler();
1430 s.add_stream(1, 10, 1)
1431 .expect("test: add_stream should succeed");
1432 assert!((s.compute_fairness() - 1.0).abs() < 1e-9);
1433 }
1434
1435 #[test]
1436 fn test_fairness_two_equal_streams() {
1437 let mut s = make_scheduler();
1438 s.add_stream(1, 10, 1)
1439 .expect("test: add_stream 1 should succeed");
1440 s.add_stream(2, 10, 1)
1441 .expect("test: add_stream 2 should succeed");
1442 s.enqueue_bytes(1, 10000)
1443 .expect("test: enqueue_bytes for stream 1 should succeed");
1444 s.enqueue_bytes(2, 10000)
1445 .expect("test: enqueue_bytes for stream 2 should succeed");
1446 s.schedule_batch(&SpsSchedulingPolicy::WeightedFairQueuing, 200);
1447 let f = s.compute_fairness();
1448 assert!(f > 0.0 && f <= 1.0);
1450 }
1451
1452 #[test]
1453 fn test_fairness_bounds() {
1454 let mut s = make_scheduler();
1455 for i in 1u64..=4 {
1456 s.add_stream(i, 10, 1)
1457 .expect("test: add_stream should succeed");
1458 s.enqueue_bytes(i, 5000)
1459 .expect("test: enqueue_bytes should succeed");
1460 }
1461 s.schedule_batch(&SpsSchedulingPolicy::WeightedFairQueuing, 200);
1462 let f = s.compute_fairness();
1463 assert!((0.0..=1.0 + 1e-9).contains(&f));
1464 }
1465
1466 #[test]
1469 fn test_stats_initial_zeros() {
1470 let mut s = make_scheduler();
1471 let stats = s.scheduler_stats();
1472 assert_eq!(stats.total_scheduled, 0);
1473 assert_eq!(stats.total_bytes, 0);
1474 assert_eq!(stats.active_streams, 0);
1475 }
1476
1477 #[test]
1478 fn test_stats_after_scheduling() {
1479 let mut s = make_scheduler();
1480 s.add_stream(1, 10, 1)
1481 .expect("test: add_stream should succeed");
1482 s.enqueue_bytes(1, 1500)
1483 .expect("test: enqueue_bytes should succeed");
1484 s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1485 let stats = s.scheduler_stats();
1486 assert!(stats.total_scheduled > 0);
1487 assert!(stats.total_bytes > 0);
1488 }
1489
1490 #[test]
1491 fn test_stats_priority_distribution() {
1492 let mut s = make_scheduler();
1493 s.add_stream(1, 42, 1)
1494 .expect("test: add_stream should succeed");
1495 s.enqueue_bytes(1, 1500)
1496 .expect("test: enqueue_bytes should succeed");
1497 s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1498 let stats = s.scheduler_stats();
1499 assert!(stats.priority_distribution.contains_key(&42));
1500 }
1501
1502 #[test]
1503 fn test_stats_active_and_blocked() {
1504 let mut s = make_scheduler();
1505 s.add_stream(1, 10, 1)
1506 .expect("test: add_stream 1 should succeed");
1507 s.add_stream(2, 10, 1)
1508 .expect("test: add_stream 2 should succeed");
1509 s.block_stream(1)
1510 .expect("test: block_stream should succeed");
1511 let stats = s.scheduler_stats();
1512 assert_eq!(stats.active_streams, 2);
1513 assert_eq!(stats.blocked_streams, 1);
1514 }
1515
1516 #[test]
1519 fn test_drain_stream() {
1520 let mut s = make_scheduler();
1521 s.add_stream(1, 10, 1)
1522 .expect("test: add_stream should succeed");
1523 s.enqueue_bytes(1, 5000)
1524 .expect("test: enqueue_bytes should succeed");
1525 let drained = s
1526 .drain_stream(1)
1527 .expect("test: drain_stream should succeed");
1528 assert_eq!(drained, 5000);
1529 assert_eq!(
1530 s.get_stream(1)
1531 .expect("test: stream 1 should exist")
1532 .pending_bytes,
1533 0
1534 );
1535 }
1536
1537 #[test]
1538 fn test_drain_nonexistent_errors() {
1539 let mut s = make_scheduler();
1540 assert!(matches!(
1541 s.drain_stream(99),
1542 Err(SpsError::StreamNotFound(99))
1543 ));
1544 }
1545
1546 #[test]
1549 fn test_update_priority() {
1550 let mut s = make_scheduler();
1551 s.add_stream(1, 10, 1)
1552 .expect("test: add_stream should succeed");
1553 s.enqueue_bytes(1, 500)
1554 .expect("test: enqueue_bytes should succeed");
1555 s.update_priority(1, 200)
1556 .expect("test: update_priority should succeed");
1557 assert_eq!(
1558 s.get_stream(1)
1559 .expect("test: stream 1 should exist")
1560 .priority,
1561 200
1562 );
1563 }
1564
1565 #[test]
1566 fn test_update_priority_nonexistent_errors() {
1567 let mut s = make_scheduler();
1568 assert!(matches!(
1569 s.update_priority(99, 100),
1570 Err(SpsError::StreamNotFound(99))
1571 ));
1572 }
1573
1574 #[test]
1575 fn test_update_priority_affects_scheduling() {
1576 let mut s = make_scheduler();
1577 s.add_stream(1, 10, 1)
1578 .expect("test: add_stream 1 should succeed");
1579 s.add_stream(2, 10, 1)
1580 .expect("test: add_stream 2 should succeed");
1581 s.enqueue_bytes(1, 5000)
1582 .expect("test: enqueue_bytes for stream 1 should succeed");
1583 s.enqueue_bytes(2, 5000)
1584 .expect("test: enqueue_bytes for stream 2 should succeed");
1585 s.update_priority(2, 200)
1586 .expect("test: update_priority should succeed");
1587 s.enqueue_bytes(2, 0).unwrap_or_default();
1589 let (id, _) = s
1590 .schedule_next(&SpsSchedulingPolicy::StrictPriority)
1591 .expect("test: schedule_next should return Some");
1592 assert_eq!(id, 2);
1593 }
1594
1595 #[test]
1598 fn test_get_stream_existing() {
1599 let mut s = make_scheduler();
1600 s.add_stream(5, 10, 2)
1601 .expect("test: add_stream should succeed");
1602 let stream = s.get_stream(5);
1603 assert!(stream.is_some());
1604 assert_eq!(stream.expect("test: stream 5 should exist").id, 5);
1605 }
1606
1607 #[test]
1608 fn test_get_stream_nonexistent() {
1609 let s = make_scheduler();
1610 assert!(s.get_stream(99).is_none());
1611 }
1612
1613 #[test]
1614 fn test_get_stream_mut() {
1615 let mut s = make_scheduler();
1616 s.add_stream(1, 10, 1)
1617 .expect("test: add_stream should succeed");
1618 let stream = s
1619 .get_stream_mut(1)
1620 .expect("test: stream 1 should exist for mutable access");
1621 stream.weight = 99;
1622 assert_eq!(
1623 s.get_stream(1).expect("test: stream 1 should exist").weight,
1624 99
1625 );
1626 }
1627
1628 #[test]
1631 fn test_xorshift64_non_zero() {
1632 let mut state = 0xdeadbeef_u64;
1633 let v = xorshift64(&mut state);
1634 assert_ne!(v, 0);
1635 }
1636
1637 #[test]
1638 fn test_xorshift64_different_on_repeated_calls() {
1639 let mut state = 12345678u64;
1640 let a = xorshift64(&mut state);
1641 let b = xorshift64(&mut state);
1642 assert_ne!(a, b);
1643 }
1644
1645 #[test]
1648 fn test_eligible_count() {
1649 let mut s = make_scheduler();
1650 s.add_stream(1, 10, 1)
1651 .expect("test: add_stream 1 should succeed");
1652 s.add_stream(2, 10, 1)
1653 .expect("test: add_stream 2 should succeed");
1654 s.enqueue_bytes(1, 100)
1655 .expect("test: enqueue_bytes should succeed");
1656 assert_eq!(s.eligible_count(), 1);
1657 }
1658
1659 #[test]
1660 fn test_tick_increments() {
1661 let mut s = make_scheduler();
1662 s.add_stream(1, 10, 1)
1663 .expect("test: add_stream should succeed");
1664 s.enqueue_bytes(1, 3000)
1665 .expect("test: enqueue_bytes should succeed");
1666 let t0 = s.tick();
1667 s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1668 assert!(s.tick() > t0);
1669 }
1670
1671 #[test]
1672 fn test_bytes_sent_accumulates() {
1673 let mut s = make_scheduler();
1674 s.add_stream(1, 10, 1)
1675 .expect("test: add_stream should succeed");
1676 s.enqueue_bytes(1, 3000)
1677 .expect("test: enqueue_bytes should succeed");
1678 s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1679 assert!(
1680 s.get_stream(1)
1681 .expect("test: stream 1 should exist")
1682 .bytes_sent
1683 > 0
1684 );
1685 }
1686
1687 #[test]
1688 fn test_send_count_increments() {
1689 let mut s = make_scheduler();
1690 s.add_stream(1, 10, 1)
1691 .expect("test: add_stream should succeed");
1692 s.enqueue_bytes(1, 1500)
1693 .expect("test: enqueue_bytes should succeed");
1694 s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1695 assert_eq!(
1696 s.get_stream(1)
1697 .expect("test: stream 1 should exist")
1698 .send_count,
1699 1
1700 );
1701 }
1702
1703 #[test]
1704 fn test_multiple_policies_same_scheduler() {
1705 let mut s = make_scheduler();
1706 s.add_stream(1, 10, 1)
1707 .expect("test: add_stream 1 should succeed");
1708 s.add_stream(2, 20, 2)
1709 .expect("test: add_stream 2 should succeed");
1710 s.enqueue_bytes(1, 5000)
1711 .expect("test: enqueue_bytes for stream 1 should succeed");
1712 s.enqueue_bytes(2, 5000)
1713 .expect("test: enqueue_bytes for stream 2 should succeed");
1714 let r1 = s.schedule_next(&SpsSchedulingPolicy::StrictPriority);
1716 let r2 = s.schedule_next(&SpsSchedulingPolicy::WeightedFairQueuing);
1717 let r3 = s.schedule_next(&SpsSchedulingPolicy::EarliestDeadlineFirst);
1718 assert!(r1.is_some() || r2.is_some() || r3.is_some());
1719 }
1720
1721 #[test]
1722 fn test_large_enqueue_no_overflow() {
1723 let mut s = make_scheduler();
1724 s.add_stream(1, 10, 1)
1725 .expect("test: add_stream should succeed");
1726 s.enqueue_bytes(1, u64::MAX / 2)
1727 .expect("test: enqueue_bytes should succeed");
1728 s.enqueue_bytes(1, 1).unwrap_or_default(); }
1731
1732 #[test]
1733 fn test_streams_removed_counter() {
1734 let mut s = make_scheduler();
1735 s.add_stream(1, 10, 1)
1736 .expect("test: add_stream should succeed");
1737 s.remove_stream(1)
1738 .expect("test: remove_stream should succeed");
1739 let stats = s.scheduler_stats();
1740 assert_eq!(stats.streams_removed, 1);
1741 }
1742
1743 #[test]
1744 fn test_htb_refill_clamps_to_burst() {
1745 let mut s = make_scheduler();
1746 s.add_stream(1, 10, 1)
1747 .expect("test: add_stream should succeed");
1748 s.set_htb_params(1, 1000, 5000)
1749 .expect("test: set_htb_params should succeed");
1750 s.htb_refill(1000); let tok = s
1752 .get_stream(1)
1753 .expect("test: stream 1 should exist")
1754 .htb_tokens;
1755 assert_eq!(tok, 5000);
1756 }
1757
1758 #[test]
1759 fn test_schedule_all_policies_no_eligible_returns_none() {
1760 let mut s = make_scheduler();
1761 let policies = [
1762 SpsSchedulingPolicy::StrictPriority,
1763 SpsSchedulingPolicy::WeightedFairQueuing,
1764 SpsSchedulingPolicy::DeficitRoundRobin,
1765 SpsSchedulingPolicy::EarliestDeadlineFirst,
1766 SpsSchedulingPolicy::HierarchicalToken,
1767 ];
1768 for policy in &policies {
1769 assert!(
1770 s.schedule_next(policy).is_none(),
1771 "policy {:?} should return None",
1772 policy
1773 );
1774 }
1775 }
1776
1777 #[test]
1778 fn test_edf_multiple_streams_ordering() {
1779 let mut s = make_scheduler();
1780 let deadlines = [(1u64, 300u64), (2, 100), (3, 200), (4, 50)];
1781 for (id, dl) in &deadlines {
1782 s.add_stream(*id, 10, 1)
1783 .expect("test: add_stream should succeed");
1784 s.set_deadline(*id, *dl)
1785 .expect("test: set_deadline should succeed");
1786 s.enqueue_bytes(*id, 1500)
1787 .expect("test: enqueue_bytes should succeed");
1788 }
1789 let (first, _) = s
1790 .schedule_next(&SpsSchedulingPolicy::EarliestDeadlineFirst)
1791 .expect("test: schedule_next should return Some");
1792 assert_eq!(first, 4); }
1794
1795 #[test]
1796 fn test_wfq_no_eligible_returns_none() {
1797 let mut s = make_scheduler();
1798 s.add_stream(1, 10, 1)
1799 .expect("test: add_stream should succeed"); let r = s.schedule_next(&SpsSchedulingPolicy::WeightedFairQueuing);
1801 assert!(r.is_none());
1802 }
1803
1804 #[test]
1805 fn test_drr_empty_active_list_rebuilt() {
1806 let mut s = make_scheduler();
1807 s.add_stream(1, 10, 1)
1808 .expect("test: add_stream should succeed");
1809 s.enqueue_bytes(1, 500)
1810 .expect("test: enqueue_bytes should succeed");
1811 let r1 = s.schedule_next(&SpsSchedulingPolicy::DeficitRoundRobin);
1813 assert!(r1.is_some());
1814 }
1815
1816 #[test]
1817 fn test_fairness_index_in_stats() {
1818 let mut s = make_scheduler();
1819 s.add_stream(1, 10, 1)
1820 .expect("test: add_stream 1 should succeed");
1821 s.add_stream(2, 10, 1)
1822 .expect("test: add_stream 2 should succeed");
1823 s.enqueue_bytes(1, 5000)
1824 .expect("test: enqueue_bytes for stream 1 should succeed");
1825 s.enqueue_bytes(2, 5000)
1826 .expect("test: enqueue_bytes for stream 2 should succeed");
1827 s.schedule_batch(&SpsSchedulingPolicy::WeightedFairQueuing, 50);
1828 let stats = s.scheduler_stats();
1829 assert!((0.0..=1.0 + 1e-9).contains(&stats.fairness_index));
1830 }
1831
1832 #[test]
1833 fn test_update_priority_no_pending_no_queue_change() {
1834 let mut s = make_scheduler();
1835 s.add_stream(1, 10, 1)
1836 .expect("test: add_stream should succeed");
1837 s.update_priority(1, 50)
1839 .expect("test: update_priority should succeed");
1840 assert_eq!(
1841 s.get_stream(1)
1842 .expect("test: stream 1 should exist")
1843 .priority,
1844 50
1845 );
1846 }
1847
1848 #[test]
1849 fn test_scheduler_stats_avg_wait_zero_before_any_schedule() {
1850 let mut s = make_scheduler();
1851 s.add_stream(1, 10, 1)
1852 .expect("test: add_stream should succeed");
1853 let stats = s.scheduler_stats();
1854 assert_eq!(stats.avg_wait, 0.0);
1855 }
1856
1857 #[test]
1858 fn test_drr_run_round_with_no_streams() {
1859 let mut s = make_scheduler();
1860 let results = s.run_drr_round();
1861 assert!(results.is_empty());
1862 }
1863
1864 #[test]
1865 fn test_blocked_stream_not_scheduled() {
1866 let mut s = make_scheduler();
1867 s.add_stream(1, 100, 1)
1868 .expect("test: add_stream 1 should succeed");
1869 s.add_stream(2, 10, 1)
1870 .expect("test: add_stream 2 should succeed");
1871 s.enqueue_bytes(1, 5000)
1872 .expect("test: enqueue_bytes for stream 1 should succeed");
1873 s.enqueue_bytes(2, 5000)
1874 .expect("test: enqueue_bytes for stream 2 should succeed");
1875 s.block_stream(1)
1876 .expect("test: block_stream should succeed");
1877 let (id, _) = s
1878 .schedule_next(&SpsSchedulingPolicy::StrictPriority)
1879 .expect("test: schedule_next should return Some");
1880 assert_eq!(id, 2); }
1882
1883 #[test]
1884 fn test_stream_is_eligible_after_enqueue() {
1885 let mut s = make_scheduler();
1886 s.add_stream(1, 10, 1)
1887 .expect("test: add_stream should succeed");
1888 assert!(!s
1889 .get_stream(1)
1890 .expect("test: stream 1 should exist")
1891 .is_eligible());
1892 s.enqueue_bytes(1, 100)
1893 .expect("test: enqueue_bytes should succeed");
1894 assert!(s
1895 .get_stream(1)
1896 .expect("test: stream 1 should exist")
1897 .is_eligible());
1898 }
1899
1900 #[test]
1901 fn test_stream_not_eligible_when_blocked_even_with_data() {
1902 let mut s = make_scheduler();
1903 s.add_stream(1, 10, 1)
1904 .expect("test: add_stream should succeed");
1905 s.enqueue_bytes(1, 100)
1906 .expect("test: enqueue_bytes should succeed");
1907 s.block_stream(1)
1908 .expect("test: block_stream should succeed");
1909 assert!(!s
1910 .get_stream(1)
1911 .expect("test: stream 1 should exist")
1912 .is_eligible());
1913 }
1914
1915 #[test]
1916 fn test_sps_stream_new_defaults() {
1917 let stream = SpsStream::new(42, 100, 5);
1918 assert_eq!(stream.id, 42);
1919 assert_eq!(stream.priority, 100);
1920 assert_eq!(stream.weight, 5);
1921 assert_eq!(stream.pending_bytes, 0);
1922 assert!(!stream.is_blocked);
1923 }
1924
1925 #[test]
1926 fn test_sps_error_display() {
1927 let e = SpsError::StreamNotFound(5);
1928 assert!(e.to_string().contains("5"));
1929 }
1930
1931 #[test]
1932 fn test_add_multiple_streams_different_priorities() {
1933 let mut s = make_scheduler();
1934 for i in 0u64..10 {
1935 s.add_stream(i, (i * 10) as u32, 1)
1936 .expect("test: add_stream should succeed");
1937 }
1938 assert_eq!(s.stream_count(), 10);
1939 }
1940
1941 #[test]
1942 fn test_remove_and_readd_same_id() {
1943 let mut s = make_scheduler();
1944 s.add_stream(1, 10, 1)
1945 .expect("test: add_stream should succeed");
1946 s.remove_stream(1)
1947 .expect("test: first remove_stream should succeed");
1948 s.add_stream(1, 20, 2)
1949 .expect("test: re-add_stream should succeed");
1950 assert_eq!(
1951 s.get_stream(1)
1952 .expect("test: stream 1 should exist after re-add")
1953 .priority,
1954 20
1955 );
1956 }
1957
1958 #[test]
1959 fn test_htb_fallback_with_no_tokens() {
1960 let mut s = make_scheduler();
1961 s.add_stream(1, 10, 1)
1962 .expect("test: add_stream 1 should succeed");
1963 s.add_stream(2, 20, 1)
1964 .expect("test: add_stream 2 should succeed");
1965 s.enqueue_bytes(1, 5000)
1966 .expect("test: enqueue_bytes for stream 1 should succeed");
1967 s.enqueue_bytes(2, 5000)
1968 .expect("test: enqueue_bytes for stream 2 should succeed");
1969 let (id, _) = s
1971 .schedule_next(&SpsSchedulingPolicy::HierarchicalToken)
1972 .expect("test: schedule_next should return Some");
1973 assert_eq!(id, 2); }
1975}