1use crate::{broadcast, cache, group, stats, track};
2use kio::Pollable;
3use std::{
4 cmp::Reverse,
5 collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
6 fmt,
7 sync::Arc,
8 sync::atomic::{AtomicU64, Ordering},
9 task::{Poll, ready},
10 time::Duration,
11};
12
13use rand::RngExt;
14
15use super::{
16 Requests, WeakCache, WeakEntry,
17 front::{Action, Candidate, Event, Front, Pin, Refusal},
18};
19use crate::{
20 AsPath, Error, InvalidPattern, Path, PathOwned, Pattern, Patterns,
21 coding::{BoundsExceeded, Decode, DecodeError, Encode, EncodeError},
22 path::Segment,
23 runtime::{Instant, Timers},
24 time::Clock,
25 util::{Keepalive, TaskSet, Tasks, TasksWeak},
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct Hop {
39 id: u64,
41}
42
43impl Hop {
44 pub const UNKNOWN: Self = Self { id: 0 };
51
52 pub fn new(id: u64) -> Result<Self, InvalidHop> {
58 if id == 0 || id >= 1u64 << 62 {
59 return Err(InvalidHop::Range);
60 }
61 Ok(Self { id })
62 }
63
64 pub fn random() -> Self {
71 let mut rng = rand::rng();
72 let id = rng.random_range(1..(1u64 << 53));
73 Self { id }
74 }
75
76 pub fn id(self) -> u64 {
78 self.id
79 }
80
81 pub(crate) fn from_wire(id: u64) -> Result<Self, DecodeError> {
83 if id >= 1u64 << 62 {
84 return Err(DecodeError::InvalidValue);
85 }
86 Ok(Self { id })
87 }
88}
89
90#[derive(Clone, Debug)]
96#[non_exhaustive]
97pub struct Config {
98 pub hop: Hop,
101
102 pub pool: cache::Pool,
109
110 pub cache_duration: Duration,
118
119 pub default_max_age: Duration,
129
130 pub update_hold: Duration,
141}
142
143pub const DEFAULT_UPDATE_HOLD: Duration = Duration::from_millis(300);
146
147impl Default for Config {
148 fn default() -> Self {
150 let pool = cache::Pool::new(cache::Config::default().with_expiry(cache::DEFAULT_EXPIRY));
151 Self {
152 hop: Hop::random(),
153 pool,
154 cache_duration: Duration::MAX,
155 default_max_age: track::DEFAULT_MAX_AGE,
156 update_hold: DEFAULT_UPDATE_HOLD,
157 }
158 }
159}
160
161impl Config {
162 pub fn new(hop: Hop) -> Self {
164 Self { hop, ..Self::default() }
165 }
166}
167
168impl From<Hop> for Config {
169 fn from(hop: Hop) -> Self {
171 Self::new(hop)
172 }
173}
174
175impl TryFrom<u64> for Hop {
176 type Error = InvalidHop;
177
178 fn try_from(id: u64) -> Result<Self, Self::Error> {
179 Self::new(id)
180 }
181}
182
183impl fmt::Display for Hop {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 self.id.fmt(f)
186 }
187}
188
189impl<V: Copy> Encode<V> for Hop
190where
191 u64: Encode<V>,
192{
193 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
194 self.id.encode(w, version)
195 }
196}
197
198impl<V: Copy> Decode<V> for Hop
199where
200 u64: Decode<V>,
201{
202 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
203 Self::from_wire(u64::decode(r, version)?)
204 }
205}
206
207pub(crate) const MAX_HOPS: usize = 32;
213
214#[derive(Debug, Clone, Default, PartialEq, Eq)]
222pub struct Hops(Vec<Hop>);
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226#[non_exhaustive]
227pub enum InvalidHop {
228 Range,
231
232 TooMany,
235
236 Duplicate,
240}
241
242impl fmt::Display for InvalidHop {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 Self::Range => write!(f, "local hop id must be non-zero and below 2^62"),
246 Self::TooMany => write!(f, "too many hops (max {MAX_HOPS})"),
247 Self::Duplicate => write!(f, "hop already in the chain"),
248 }
249 }
250}
251
252impl std::error::Error for InvalidHop {}
253
254impl From<InvalidHop> for DecodeError {
255 fn from(err: InvalidHop) -> Self {
256 match err {
257 InvalidHop::TooMany => DecodeError::BoundsExceeded,
258 InvalidHop::Range | InvalidHop::Duplicate => DecodeError::InvalidValue,
259 }
260 }
261}
262
263impl Hops {
264 pub fn new() -> Self {
266 Self(Vec::new())
267 }
268
269 pub fn push(&mut self, hop: Hop) -> Result<(), InvalidHop> {
275 if self.0.len() >= MAX_HOPS {
276 return Err(InvalidHop::TooMany);
277 }
278 if hop != Hop::UNKNOWN && self.0.contains(&hop) {
279 return Err(InvalidHop::Duplicate);
280 }
281 self.0.push(hop);
282 Ok(())
283 }
284
285 pub(crate) fn stamp(&mut self, stamp: Hop) -> Result<(), InvalidHop> {
294 match self.0.first() {
295 None => {
296 self.push(stamp)?;
297 self.push(Hop::UNKNOWN)
298 }
299 Some(first) if *first == Hop::UNKNOWN => {
300 if self.0.len() >= MAX_HOPS {
301 return Err(InvalidHop::TooMany);
302 }
303 if self.0.contains(&stamp) {
304 return Err(InvalidHop::Duplicate);
305 }
306 self.0.insert(0, stamp);
307 Ok(())
308 }
309 Some(_) => Ok(()),
310 }
311 }
312
313 pub fn contains(&self, hop: &Hop) -> bool {
315 self.0.contains(hop)
316 }
317
318 pub fn len(&self) -> usize {
320 self.0.len()
321 }
322
323 pub fn is_empty(&self) -> bool {
325 self.0.is_empty()
326 }
327
328 pub fn iter(&self) -> std::slice::Iter<'_, Hop> {
330 self.0.iter()
331 }
332
333 pub fn as_slice(&self) -> &[Hop] {
335 &self.0
336 }
337}
338
339impl TryFrom<Vec<Hop>> for Hops {
340 type Error = InvalidHop;
341
342 fn try_from(v: Vec<Hop>) -> Result<Self, Self::Error> {
343 if v.len() > MAX_HOPS {
344 return Err(InvalidHop::TooMany);
345 }
346 for (i, hop) in v.iter().enumerate() {
348 if *hop != Hop::UNKNOWN && v[i + 1..].contains(hop) {
349 return Err(InvalidHop::Duplicate);
350 }
351 }
352 Ok(Self(v))
353 }
354}
355
356impl<'a> IntoIterator for &'a Hops {
357 type Item = &'a Hop;
358 type IntoIter = std::slice::Iter<'a, Hop>;
359
360 fn into_iter(self) -> Self::IntoIter {
361 self.iter()
362 }
363}
364
365impl<V: Copy> Encode<V> for Hops
366where
367 u64: Encode<V>,
368 Hop: Encode<V>,
369{
370 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
371 (self.0.len() as u64).encode(w, version)?;
372 for origin in &self.0 {
373 origin.encode(w, version)?;
374 }
375 Ok(())
376 }
377}
378
379impl<V: Copy> Decode<V> for Hops
380where
381 u64: Decode<V>,
382 Hop: Decode<V>,
383{
384 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
385 let count = u64::decode(r, version)? as usize;
386 if count > MAX_HOPS {
387 return Err(DecodeError::BoundsExceeded);
388 }
389 let mut list = Self(Vec::with_capacity(count));
392 for _ in 0..count {
393 list.push(Hop::decode(r, version)?)?;
394 }
395 Ok(list)
396 }
397}
398
399const MAX_COST: u64 = (1 << 62) - 1;
407
408#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
417pub struct Cost {
418 pub warm: u64,
427
428 pub cold: u64,
435}
436
437impl Cost {
438 pub const fn new(cost: u64) -> Self {
441 Self { warm: cost, cold: cost }
442 }
443
444 pub const MAX: Self = Self::new(MAX_COST);
451
452 pub const DRAIN: Self = Self::MAX;
455
456 pub(crate) const UNKNOWN: Self = Self {
460 warm: 0,
461 cold: MAX_COST,
462 };
463
464 pub(crate) fn charged(self, link_cost: u64) -> Self {
467 Self {
468 warm: self.warm.saturating_add(link_cost).min(MAX_COST),
469 cold: self.cold.saturating_add(link_cost).min(MAX_COST),
470 }
471 }
472
473 pub(crate) fn clamped(self) -> Self {
476 Self {
477 warm: self.warm.min(MAX_COST),
478 cold: self.cold.min(MAX_COST),
479 }
480 }
481}
482
483impl From<u64> for Cost {
484 fn from(cost: u64) -> Self {
485 Self::new(cost)
486 }
487}
488
489#[derive(Clone, Debug, PartialEq, Eq)]
500#[non_exhaustive]
501pub struct Route {
502 pub hops: Hops,
508
509 pub cost: Cost,
514
515 pub(crate) via: Hop,
521
522 pub(crate) source: Source,
524}
525
526impl Default for Route {
527 fn default() -> Self {
528 Self {
529 hops: Hops::new(),
530 cost: Cost::default(),
531 via: Hop::UNKNOWN,
532 source: Source::Local,
533 }
534 }
535}
536
537#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
543pub enum Source {
544 #[default]
546 Local,
547 Peer(Hop),
550}
551
552impl Route {
553 pub fn with_hops(mut self, hops: Hops) -> Self {
555 self.hops = hops;
556 self
557 }
558
559 pub fn with_cost(mut self, cost: impl Into<Cost>) -> Self {
564 self.cost = cost.into();
565 self
566 }
567
568 pub(crate) fn with_via(mut self, via: Hop) -> Self {
573 self.via = via;
574 self
575 }
576
577 pub fn is_anonymous(&self) -> bool {
585 self.hops.iter().any(|hop| *hop == Hop::UNKNOWN)
586 }
587
588 pub fn source(&self) -> Source {
593 self.source
594 }
595}
596
597static NEXT_CONSUMER_ID: AtomicU64 = AtomicU64::new(0);
598
599#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
600struct ConsumerId(u64);
601
602impl ConsumerId {
603 fn new() -> Self {
604 Self(NEXT_CONSUMER_ID.fetch_add(1, Ordering::Relaxed))
605 }
606}
607
608fn fnv_key(name: &str, origins: impl IntoIterator<Item = Hop>) -> u64 {
617 const SEED: u64 = 0x420C0DECB00B; const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
619
620 let mut hash = SEED;
621 for &byte in name.as_bytes() {
622 hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
623 }
624 for origin in origins {
625 for &byte in &origin.id().to_le_bytes() {
626 hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
627 }
628 }
629
630 hash
631}
632
633fn route_order(prefix: &Path, entry: &RouteEntry) -> (bool, Cost, bool, usize, u64, Reverse<u64>) {
641 (
642 entry.is_anonymous(),
643 entry.cost,
644 !entry.local,
645 entry.hops.len(),
646 fnv_key(prefix.as_str(), entry.hops.iter().copied()),
647 Reverse(entry.id),
648 )
649}
650
651type RouteMeta = (Hops, Cost, Source);
653
654#[derive(Clone, Copy)]
661enum Held {
662 Anchoring(Instant),
663 Until(Instant),
664}
665
666type AnnounceMeta = (RouteMeta, Option<Vec<Pattern>>);
673
674enum PendingUpdate {
675 Announce(AnnounceMeta),
676 Unannounce(AnnounceMeta),
677 UnannounceAnnounce { old: AnnounceMeta, new: AnnounceMeta },
678}
679
680#[derive(Default)]
685struct OriginConsumerState {
686 pending: BTreeMap<PathOwned, PendingUpdate>,
687 delivered: BTreeSet<PathOwned>,
692 ended: bool,
695}
696
697impl OriginConsumerState {
698 fn apply_announce(&mut self, prefix: PathOwned, meta: RouteMeta, captures: Option<Vec<Pattern>>) {
699 let meta = (meta, captures);
700 let new = match self.pending.remove(&prefix) {
701 None | Some(PendingUpdate::Announce(_)) => PendingUpdate::Announce(meta),
703 Some(PendingUpdate::Unannounce(old) | PendingUpdate::UnannounceAnnounce { old, .. }) => {
705 PendingUpdate::UnannounceAnnounce { old, new: meta }
706 }
707 };
708 self.pending.insert(prefix, new);
709 }
710
711 fn apply_unannounce(&mut self, prefix: PathOwned, last: RouteMeta, captures: Option<Vec<Pattern>>) {
712 let last = (last, captures);
713 match self.pending.remove(&prefix) {
714 Some(PendingUpdate::Announce(_)) if !self.delivered.contains(&prefix) => {}
717 None | Some(PendingUpdate::Announce(_) | PendingUpdate::Unannounce(_)) => {
720 self.pending.insert(prefix, PendingUpdate::Unannounce(last));
721 }
722 Some(PendingUpdate::UnannounceAnnounce { old, .. }) => {
725 self.pending.insert(prefix, PendingUpdate::Unannounce(old));
726 }
727 }
728 }
729
730 fn is_update(&self, prefix: &PathOwned, update: &PendingUpdate) -> bool {
733 matches!(update, PendingUpdate::Announce(_)) && self.delivered.contains(prefix)
734 }
735
736 fn scan(
739 &self,
740 held: &mut HashMap<PathOwned, Held>,
741 now: Option<Instant>,
742 hold: Duration,
743 ) -> Result<PathOwned, Option<Instant>> {
744 let mut wake = None;
745 for (prefix, update) in &self.pending {
746 let holds = !hold.is_zero() && !self.ended && self.is_update(prefix, update);
747 let Some(now) = now.filter(|_| holds) else {
748 return Ok(prefix.clone());
749 };
750 let until = match held.get(prefix).copied() {
753 None => {
754 held.insert(prefix.clone(), Held::Anchoring(now));
755 now + Duration::from_nanos(1)
756 }
757 Some(Held::Anchoring(seen)) if now <= seen => seen + Duration::from_nanos(1),
758 Some(Held::Anchoring(_)) => {
759 held.insert(prefix.clone(), Held::Until(now + hold));
760 now + hold
761 }
762 Some(Held::Until(until)) if until <= now => return Ok(prefix.clone()),
763 Some(Held::Until(until)) => until,
764 };
765 wake = Some(wake.map_or(until, |w: Instant| w.min(until)));
766 }
767 Err(wake)
768 }
769
770 fn take_prefix(&mut self, prefix: PathOwned) -> Option<AnnounceUpdate> {
772 let ((meta, captures), kind) = match self.pending.remove(&prefix).unwrap() {
773 PendingUpdate::Announce(meta) => {
774 let kind = match self.delivered.insert(prefix.clone()) {
776 true => AnnounceKind::Announced,
777 false => AnnounceKind::Updated,
778 };
779 (meta, kind)
780 }
781 PendingUpdate::Unannounce(meta) => {
782 self.delivered.remove(&prefix);
783 (meta, AnnounceKind::Retracted)
784 }
785 PendingUpdate::UnannounceAnnounce { old, new } => {
786 self.delivered.remove(&prefix);
789 self.pending.insert(prefix.clone(), PendingUpdate::Announce(new));
790 (old, AnnounceKind::Retracted)
791 }
792 };
793 Some(AnnounceUpdate {
794 prefix,
795 captures,
796 route: Route {
797 hops: meta.0,
798 cost: meta.1,
799 via: Hop::UNKNOWN,
800 source: meta.2,
801 },
802 kind,
803 })
804 }
805}
806
807struct RouteEntry {
809 id: u64,
810 prefix: PathOwned,
811 scope: Patterns,
814 hops: Hops,
815 cost: Cost,
816 via: Hop,
820 local: bool,
823 peer: bool,
826 server: Option<kio::Shared<ServeState>>,
830 source: Option<broadcast::Consumer>,
834 advertised: bool,
838 stale: bool,
842 claim: Pattern,
848}
849
850impl RouteEntry {
851 fn live(&self) -> bool {
853 self.advertised && !self.stale
854 }
855
856 fn is_anonymous(&self) -> bool {
857 self.hops.iter().any(|hop| *hop == Hop::UNKNOWN)
858 }
859
860 fn entered(&self) -> Source {
862 match self.peer {
863 true => Source::Peer(self.via),
864 false => Source::Local,
865 }
866 }
867
868 fn serves(&self, path: &Path) -> bool {
872 self.server.is_some() || (self.source.is_some() && self.prefix == *path)
873 }
874
875 fn qualifies(&self, pin: Pin) -> bool {
877 match pin {
878 Pin::Any => true,
879 Pin::Local => self.local,
880 Pin::Publisher(first) => self.hops.iter().next() == Some(&first),
881 Pin::Route(id) => self.id == id && self.hops.iter().next().is_none_or(|first| *first == Hop::UNKNOWN),
883 }
884 }
885
886 fn visible_to(&self, exclude: Option<Hop>) -> bool {
891 match exclude {
892 Some(peer) if peer != Hop::UNKNOWN => self.via != peer && !self.hops.contains(&peer),
893 _ => true,
894 }
895 }
896
897 fn overlaps(&self, allowed: &Patterns) -> bool {
899 self.scope.iter().any(|scope| {
900 scope
901 .intersect(&self.claim)
902 .is_ok_and(|scoped| scoped.iter().any(|restriction| allowed.overlaps(restriction)))
903 })
904 }
905}
906
907fn prefix_claim(prefix: &Path) -> Result<Pattern, InvalidPattern> {
909 if prefix.parts().count() == Path::MAX_PARTS {
910 Pattern::literal(prefix.as_str())
911 } else {
912 Pattern::subtree(prefix.as_str())
913 }
914}
915
916#[derive(Default)]
921struct ServeState {
922 requests: Requests<PathOwned, kio::Producer<PendingBroadcast>>,
925
926 served: WeakCache<PathOwned, broadcast::WeakConsumer>,
932
933 closed: bool,
936}
937
938type FrontKey = (PathOwned, Horizon);
945
946#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
948struct Horizon {
949 exclude: Option<Hop>,
953 local: bool,
955}
956
957impl Horizon {
958 fn admits(&self, entry: &RouteEntry) -> bool {
960 !(self.local && entry.peer) && entry.visible_to(self.exclude)
961 }
962
963 fn effective(self, routes: &RouteTable, path: &Path) -> Self {
973 match self.exclude {
974 Some(peer) if routes.covering(path).any(|entry| !entry.visible_to(Some(peer))) => self,
975 _ => Self { exclude: None, ..self },
976 }
977 }
978}
979
980#[derive(Clone)]
983struct RemoteFront {
984 request: kio::Producer<PendingBroadcast>,
988 broadcast: broadcast::WeakConsumer,
991 pin: kio::Lock<Pin>,
994}
995
996type CursorRoute = (u64, RouteMeta, bool, Option<Vec<Pattern>>);
998
999impl WeakEntry for RemoteFront {
1000 fn is_closed(&self) -> bool {
1001 self.broadcast.is_closed()
1002 }
1003
1004 fn same_channel(&self, other: &Self) -> bool {
1005 self.broadcast.same_channel(&other.broadcast)
1006 }
1007}
1008
1009struct TableCursor {
1012 root: PathOwned,
1014 allowed: Patterns,
1016 heads: Vec<PathOwned>,
1020 horizon: Horizon,
1022 hidden: Hidden,
1024 state: kio::Producer<OriginConsumerState>,
1027 under: PathOwned,
1030 holes: Vec<PathOwned>,
1033 named: Option<(Mount, Patterns)>,
1036 current: HashMap<PathOwned, CursorRoute>,
1041}
1042
1043impl TableCursor {
1044 fn presented(&self, prefix: &Path, claim: &Pattern) -> Option<PathOwned> {
1050 if !self.allowed.overlaps(claim) {
1051 return None;
1052 }
1053
1054 if let Some(relative) = prefix.strip_prefix(&self.root) {
1055 return Some(relative.to_owned());
1056 }
1057 self.root.has_prefix(prefix).then(PathOwned::default)
1058 }
1059
1060 fn captures(&self, prefix: &Path) -> Option<Vec<Pattern>> {
1063 let (literal, allowed) = match &self.named {
1064 Some((mount, allowed)) => (Pattern::literal(mount.name(prefix)?.as_str()).ok()?, allowed),
1065 None => (Pattern::literal(prefix.as_str()).ok()?, &self.allowed),
1066 };
1067 allowed
1068 .iter()
1069 .filter_map(|allowed| {
1070 allowed
1071 .captures(&literal)
1072 .map(|captures| (allowed.specificity(), captures))
1073 })
1074 .max_by_key(|(specificity, _)| *specificity)
1075 .map(|(_, captures)| captures)
1076 }
1077
1078 fn visible(&self, entry: &RouteEntry) -> bool {
1082 entry.live()
1083 && self.horizon.admits(entry)
1084 && entry.overlaps(&self.allowed)
1085 && self.discovers(&entry.prefix)
1086 && !self.holes.iter().any(|hole| entry.prefix.has_prefix(hole))
1087 && self.named.as_ref().is_none_or(|(mount, _)| mount.names(&entry.prefix))
1088 }
1089
1090 fn update(&mut self, presented: &PathOwned, best: Option<&RouteEntry>) {
1092 match best {
1093 Some(entry) => {
1094 let meta = (entry.hops.clone(), entry.cost, entry.entered());
1095 let served = entry.server.is_some();
1096 let captures = self.captures(&entry.prefix);
1097 let previous = self
1098 .current
1099 .insert(presented.clone(), (entry.id, meta.clone(), served, captures.clone()));
1100 match previous {
1101 Some((_, prev, prev_served, prev_captures))
1108 if prev == meta && prev_served == served && prev_captures == captures => {}
1109 Some((_, prev, _, prev_captures)) if prev_captures != captures => {
1112 if let Ok(mut state) = self.state.write() {
1113 state.apply_unannounce(self.under.join(presented), prev, prev_captures);
1114 state.apply_announce(self.under.join(presented), meta, captures);
1115 }
1116 }
1117 _ => {
1118 if let Ok(mut state) = self.state.write() {
1119 state.apply_announce(self.under.join(presented), meta, captures);
1120 }
1121 }
1122 }
1123 }
1124 None => {
1125 if let Some((_, last, _, captures)) = self.current.remove(presented)
1126 && let Ok(mut state) = self.state.write()
1127 {
1128 state.apply_unannounce(self.under.join(presented), last, captures);
1129 }
1130 }
1131 }
1132 }
1133
1134 fn discovers(&self, prefix: &Path) -> bool {
1136 self.hidden.discovers(&self.heads, prefix)
1137 }
1138}
1139
1140#[derive(Clone)]
1143struct OriginScope {
1144 allowed: Patterns,
1147 mounts: Arc<[Mount]>,
1149}
1150
1151#[derive(Clone, Debug)]
1154struct Mount {
1155 at: PathOwned,
1156 target: PathOwned,
1157}
1158
1159impl Mount {
1160 fn resolve(&self, path: &Path) -> Option<PathOwned> {
1163 let resolved = self.target.join(path.strip_prefix(&self.at)?);
1164 (resolved.parts().count() <= Path::MAX_PARTS).then_some(resolved)
1165 }
1166
1167 fn name(&self, path: &Path) -> Option<PathOwned> {
1170 Some(self.at.join(path.strip_prefix(&self.target)?))
1171 }
1172
1173 fn names(&self, path: &Path) -> bool {
1177 path.strip_prefix(&self.target)
1178 .is_none_or(|rest| self.at.parts().count() + rest.parts().count() <= Path::MAX_PARTS)
1179 }
1180
1181 fn translate(&self, patterns: &Patterns) -> Patterns {
1188 let target = self.target.as_str();
1189 patterns
1190 .rebase(self.at.as_str())
1191 .iter()
1192 .filter_map(|member| {
1193 member
1194 .rooted(target)
1195 .or_else(|_| {
1196 let segments = member
1197 .segments()
1198 .iter()
1199 .filter(|segment| **segment != Segment::Globstar);
1200 Pattern::new(segments.cloned())?.rooted(target)
1201 })
1202 .ok()
1203 })
1204 .collect()
1205 }
1206
1207 fn translate_head(&self, head: &Path) -> Option<PathOwned> {
1210 match head.strip_prefix(&self.at) {
1211 Some(rest) => Some(self.target.join(rest)),
1212 None => self.at.has_prefix(head).then(|| self.target.clone()),
1213 }
1214 }
1215}
1216
1217impl OriginScope {
1218 fn empty() -> Self {
1220 Self {
1221 allowed: Patterns::new(),
1222 mounts: Arc::from([]),
1223 }
1224 }
1225
1226 fn narrow(&self, patterns: &Patterns) -> Option<Self> {
1228 let allowed = self.allowed.intersect(patterns).ok()?;
1229 if allowed.is_empty() {
1230 None
1231 } else {
1232 Some(Self {
1233 allowed,
1234 mounts: self.mounts.clone(),
1235 })
1236 }
1237 }
1238
1239 fn mount(&self, path: &Path) -> Option<&Mount> {
1241 self.mounts.iter().find(|mount| path.has_prefix(&mount.at))
1242 }
1243
1244 fn resolve<'a>(&self, path: &'a Path<'a>) -> Option<Path<'a>> {
1247 match self.mount(path) {
1248 Some(mount) => mount.resolve(path),
1249 None => Some(path.borrow()),
1250 }
1251 }
1252
1253 fn publishes(&self, prefix: &Path) -> bool {
1256 self.mount(prefix).is_none()
1257 }
1258
1259 fn permits(&self, path: &Path) -> bool {
1261 self.allowed.matches(path.as_str())
1262 }
1263
1264 fn relative(&self, root: &Path) -> Patterns {
1266 self.allowed.rebase(root.as_str())
1267 }
1268}
1269
1270impl Default for OriginScope {
1271 fn default() -> Self {
1272 Self {
1273 allowed: Patterns::from(Pattern::all()),
1274 mounts: Arc::from([]),
1275 }
1276 }
1277}
1278
1279pub(crate) fn interest_prefixes(allowed: &Patterns) -> Vec<PathOwned> {
1282 let mut heads: Vec<PathOwned> = allowed
1283 .iter()
1284 .map(|pattern| Path::new(pattern.head()).to_owned())
1285 .collect();
1286 heads.sort();
1287 heads.dedup();
1288 let covered = heads.clone();
1289 heads.retain(|head| !covered.iter().any(|other| other != head && head.has_prefix(other)));
1290 heads
1291}
1292
1293#[derive(Clone, Debug, Default, PartialEq, Eq)]
1300pub(crate) struct Hidden {
1301 include: bool,
1303 from: Option<PathOwned>,
1305 beyond: Option<Vec<PathOwned>>,
1308}
1309
1310impl Hidden {
1311 fn discovers(&self, heads: &[PathOwned], prefix: &Path) -> bool {
1313 (self.include || !hides(self.from.as_ref().map(std::slice::from_ref).unwrap_or(heads), prefix))
1314 && self.beyond.as_ref().is_none_or(|outer| hides(outer, prefix))
1315 }
1316
1317 fn translate(&self, mount: &Mount) -> Self {
1321 let beyond = self.beyond.as_ref().and_then(|outer| {
1322 (!hides(outer, &mount.at)).then(|| outer.iter().filter_map(|head| mount.translate_head(head)).collect())
1323 });
1324 Self {
1325 include: self.include,
1326 from: self.from.as_ref().and_then(|head| mount.translate_head(head)),
1327 beyond,
1328 }
1329 }
1330}
1331
1332fn hides(heads: &[PathOwned], prefix: &Path) -> bool {
1335 heads
1336 .iter()
1337 .any(|head| prefix.strip_prefix(head).is_some_and(|below| below.is_hidden()))
1338}
1339
1340#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1342pub enum AnnounceKind {
1343 Announced,
1345 Updated,
1347 Retracted,
1349}
1350
1351impl AnnounceKind {
1352 pub fn is_active(self) -> bool {
1354 !matches!(self, Self::Retracted)
1355 }
1356}
1357
1358#[derive(Clone, Debug)]
1366pub struct AnnounceUpdate {
1367 pub prefix: PathOwned,
1369 pub captures: Option<Vec<Pattern>>,
1372 pub route: Route,
1375 pub kind: AnnounceKind,
1377}
1378
1379#[derive(Clone)]
1381pub struct Producer {
1382 hop: Hop,
1385
1386 scope: OriginScope,
1388
1389 root: PathOwned,
1391
1392 shared: kio::Shared<OriginState>,
1395
1396 pool: cache::Pool,
1399
1400 cache_duration: Duration,
1403
1404 default_max_age: Duration,
1407
1408 stats: stats::Session,
1412
1413 peer: bool,
1416
1417 tasks: Tasks,
1421
1422 timers: Clock,
1424}
1425
1426impl Producer {
1427 pub fn new(config: Config) -> (Self, Driver) {
1434 let (tasks, set) = TaskSet::new();
1435 let scope = OriginScope::default();
1436 let shared = kio::Shared::new(OriginState::new(config.update_hold));
1437 let timers = Clock::default();
1438 let pool = config.pool.clone();
1439 let producer = Self {
1440 hop: config.hop,
1441 scope: scope.clone(),
1442 root: PathOwned::default(),
1443 shared: shared.clone(),
1444 pool: config.pool,
1445 cache_duration: config.cache_duration,
1446 default_max_age: config.default_max_age,
1447 stats: stats::Session::default(),
1448 peer: false,
1449 tasks,
1450 timers: timers.clone(),
1451 };
1452 let driver = Driver {
1453 state: DriverState {
1454 set,
1455 shared,
1456 done: false,
1457 },
1458 timers,
1459 pool,
1460 };
1461 (producer, driver)
1462 }
1463
1464 pub fn with_stats(mut self, session: stats::Session) -> Self {
1468 self.stats = session;
1469 self
1470 }
1471
1472 pub fn peer(mut self) -> Self {
1480 self.peer = true;
1481 self
1482 }
1483
1484 pub fn config(&self) -> Config {
1486 Config {
1487 hop: self.hop,
1488 pool: self.pool.clone(),
1489 cache_duration: self.cache_duration,
1490 default_max_age: self.default_max_age,
1491 update_hold: self.shared.lock().update_hold,
1492 }
1493 }
1494
1495 pub fn hop(&self) -> Hop {
1497 self.hop
1498 }
1499
1500 pub(crate) fn default_max_age(&self) -> Duration {
1503 self.default_max_age
1504 }
1505
1506 pub(crate) fn empty(hop: Hop) -> Self {
1511 let (tasks, _) = TaskSet::new();
1514 Self {
1515 hop,
1516 scope: OriginScope::empty(),
1517 root: PathOwned::default(),
1518 shared: kio::Shared::new(OriginState::new(DEFAULT_UPDATE_HOLD)),
1519 pool: cache::Pool::default(),
1520 cache_duration: Duration::MAX,
1521 default_max_age: track::DEFAULT_MAX_AGE,
1522 stats: stats::Session::default(),
1523 peer: false,
1524 tasks,
1525 timers: Clock::default(),
1526 }
1527 }
1528
1529 pub fn create_broadcast(&self, path: impl AsPath) -> Result<broadcast::Producer, Error> {
1560 let path = path.as_path();
1561
1562 let full = self.root.join(&path).to_owned();
1563 if !self.scope.permits(&full) || !self.scope.publishes(&full) {
1564 return Err(Error::Unauthorized);
1565 }
1566 if full.parts().count() > Path::MAX_PARTS {
1570 return Err(BoundsExceeded.into());
1571 }
1572 let claim = prefix_claim(&full)?;
1575
1576 let ingress = self.stats.ingress(&full);
1578
1579 let announcing = Announcing {
1584 hop: self.hop,
1585 shared: self.shared.clone(),
1586 requested: full.clone(),
1587 prefixes: vec![(full.clone(), claim)],
1588 scope: self.scope.allowed.clone(),
1589 local: true,
1590 peer: self.peer,
1591 stats: self.stats.clone(),
1592 };
1593 let info = broadcast::Info {
1594 pool: self.pool.clone(),
1595 cache_duration: self.cache_duration,
1596 path: full,
1597 };
1598 let source = info.produce().with_stats(ingress.clone());
1599 let entry = announcing.announce(
1600 Route::default(),
1601 Serving {
1602 server: None,
1603 source: Some(source.consume()),
1604 advertised: false,
1605 },
1606 )?;
1607 Ok(source.with_announcer(Announcer {
1608 entry,
1609 ingress,
1610 _keepalive: self.tasks.keepalive(),
1611 }))
1612 }
1613
1614 pub fn publish(&self, path: impl AsPath, route: Route) -> Result<broadcast::Producer, Error> {
1616 let broadcast = self.create_broadcast(path)?;
1617 broadcast.announce(route)?;
1618 Ok(broadcast)
1619 }
1620
1621 pub(crate) fn create_source(&self, path: impl AsPath) -> broadcast::Producer {
1627 let path = path.as_path();
1628 let full = self.root.join(&path).to_owned();
1629 let ingress = self.stats.ingress(&full);
1630 broadcast::Info {
1631 pool: self.pool.clone(),
1632 cache_duration: self.cache_duration,
1633 path: full,
1634 }
1635 .produce()
1636 .with_stats(ingress)
1637 }
1638
1639 #[cfg(test)]
1648 pub(crate) fn announce(&self, prefix: impl AsPath, route: Route) -> Result<AnnounceProducer, Error> {
1649 Announcing::new(self, prefix)?.announce(
1650 route,
1651 Serving {
1652 server: None,
1653 source: None,
1654 advertised: true,
1655 },
1656 )
1657 }
1658
1659 pub fn dynamic(&self, prefix: impl AsPath, route: Route) -> Result<Dynamic, Error> {
1681 let announcing = Announcing::new(self, prefix)?;
1682 let serve = kio::Shared::<ServeState>::default();
1683 serve.lock().requests.add_handler();
1684 let announcement = announcing.announce(
1685 route,
1686 Serving {
1687 server: Some(serve.clone()),
1688 source: None,
1689 advertised: true,
1690 },
1691 )?;
1692 Ok(Dynamic {
1693 announcement,
1694 state: serve,
1695 _keepalive: self.tasks.keepalive(),
1696 })
1697 }
1698
1699 pub fn scope(&self, root: impl AsPath, patterns: &Patterns) -> Result<Producer, Error> {
1706 let root = self.root.join(root).to_owned();
1707 let rooted = patterns.rooted(root.as_str()).map_err(|_| BoundsExceeded)?;
1708 let scope = self.scope.narrow(&rooted).ok_or(Error::Unauthorized)?;
1709 Ok(Producer {
1710 hop: self.hop,
1711 scope,
1712 root,
1713 shared: self.shared.clone(),
1714 pool: self.pool.clone(),
1715 cache_duration: self.cache_duration,
1716 default_max_age: self.default_max_age,
1717 stats: self.stats.clone(),
1718 peer: self.peer,
1719 tasks: self.tasks.clone(),
1720 timers: self.timers.clone(),
1721 })
1722 }
1723
1724 pub fn mount(&self, at: impl AsPath, target: impl AsPath) -> Result<Producer, Error> {
1743 let at = self.root.join(at).to_owned();
1744 let target = self.root.join(target).to_owned();
1745 if [&at, &target]
1746 .into_iter()
1747 .any(|path| path.parts().count() > Path::MAX_PARTS)
1748 {
1749 return Err(BoundsExceeded.into());
1750 }
1751 Pattern::literal(at.as_str())?;
1753 if !self
1754 .scope
1755 .allowed
1756 .covers(&Patterns::from(Pattern::subtree(target.as_str())?))
1757 {
1758 return Err(Error::Unauthorized);
1759 }
1760 let overlaps = |a: &Path, b: &Path| a.has_prefix(b) || b.has_prefix(a);
1764 if overlaps(&at, &target)
1765 || self
1766 .scope
1767 .mounts
1768 .iter()
1769 .any(|mount| overlaps(&at, &mount.at) || overlaps(&target, &mount.at) || overlaps(&at, &mount.target))
1770 {
1771 return Err(Error::Duplicate);
1772 }
1773 let mounts = self
1774 .scope
1775 .mounts
1776 .iter()
1777 .cloned()
1778 .chain([Mount { at, target }])
1779 .collect();
1780 Ok(Producer {
1781 scope: OriginScope {
1782 allowed: self.scope.allowed.clone(),
1783 mounts,
1784 },
1785 ..self.clone()
1786 })
1787 }
1788
1789 pub fn consume(&self) -> Consumer {
1794 Consumer::from_producer(self, stats::Session::default())
1797 }
1798
1799 pub fn root(&self) -> &Path<'_> {
1801 &self.root
1802 }
1803
1804 pub fn allowed(&self) -> Patterns {
1806 self.scope.relative(&self.root)
1807 }
1808
1809 pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
1811 self.root.join(path)
1812 }
1813}
1814
1815struct Announcing {
1819 hop: Hop,
1820 shared: kio::Shared<OriginState>,
1821 requested: PathOwned,
1823 prefixes: Vec<(PathOwned, Pattern)>,
1827 scope: Patterns,
1829 local: bool,
1830 peer: bool,
1832 stats: stats::Session,
1833}
1834
1835impl Announcing {
1836 fn new(producer: &Producer, prefix: impl AsPath) -> Result<Self, Error> {
1838 let requested = producer.root.join(prefix.as_path()).to_owned();
1839 if requested.parts().count() > Path::MAX_PARTS {
1840 return Err(BoundsExceeded.into());
1841 }
1842 let claim = prefix_claim(&requested)?;
1843 if !producer.scope.allowed.overlaps(&claim) || !producer.scope.publishes(&requested) {
1844 return Err(Error::Unauthorized);
1845 }
1846 Ok(Self {
1847 hop: producer.hop,
1848 shared: producer.shared.clone(),
1849 requested: requested.clone(),
1850 prefixes: vec![(requested, claim)],
1851 scope: producer.scope.allowed.clone(),
1852 local: false,
1853 peer: producer.peer,
1854 stats: producer.stats.clone(),
1855 })
1856 }
1857
1858 fn announce(&self, route: Route, serving: Serving) -> Result<AnnounceProducer, Error> {
1859 debug_assert!(
1860 !route.hops.contains(&self.hop),
1861 "announce called with a looping hop chain",
1862 );
1863
1864 let via = route.via;
1865
1866 let mut shared = self.shared.lock();
1867 if shared.closed {
1868 return Err(Error::Closed);
1869 }
1870
1871 let mut entries = Vec::with_capacity(self.prefixes.len());
1872 for (prefix, claim) in &self.prefixes {
1873 shared.reannounced(prefix, &route.hops);
1874 let id = shared.next_route;
1875 shared.next_route += 1;
1876 let stale = shared.withdrawn_through(prefix, &route.hops);
1877 shared.routes.insert(RouteEntry {
1878 id,
1879 prefix: prefix.clone(),
1880 scope: self.scope.clone(),
1881 hops: route.hops.clone(),
1882 cost: route.cost,
1883 via,
1884 local: self.local,
1885 peer: self.peer,
1886 server: serving.server.clone(),
1887 source: serving.source.clone(),
1888 advertised: serving.advertised,
1889 stale,
1890 claim: claim.clone(),
1891 });
1892 shared.sync_route(prefix, claim);
1893 entries.push((prefix.clone(), id));
1894 }
1895 drop(shared);
1896
1897 let guard = serving
1899 .advertised
1900 .then(|| self.stats.ingress(&self.requested).announce());
1901
1902 Ok(AnnounceProducer {
1903 shared: self.shared.clone(),
1904 entries,
1905 guard,
1906 })
1907 }
1908}
1909
1910struct Serving {
1912 server: Option<kio::Shared<ServeState>>,
1913 source: Option<broadcast::Consumer>,
1914 advertised: bool,
1915}
1916
1917pub(crate) struct Announcer {
1924 entry: AnnounceProducer,
1925 ingress: stats::Scope,
1927 _keepalive: Keepalive,
1931}
1932
1933impl Announcer {
1934 pub(crate) fn announce(&mut self, route: Route) -> Result<(), Error> {
1936 self.entry.update(route)?;
1937 if self.entry.guard.is_none() {
1938 self.entry.guard = Some(self.ingress.announce());
1939 }
1940 Ok(())
1941 }
1942
1943 pub(crate) fn withdraw(&mut self) {
1945 self.entry.withdraw();
1946 self.entry.guard = None;
1947 }
1948}
1949
1950#[must_use = "dropping an announcement retracts the route"]
1957pub(crate) struct AnnounceProducer {
1958 shared: kio::Shared<OriginState>,
1959 entries: Vec<(PathOwned, u64)>,
1962 guard: Option<stats::Announce>,
1964}
1965
1966impl AnnounceProducer {
1967 pub fn update(&self, route: Route) -> Result<(), Error> {
1975 let mut shared = self.shared.lock();
1976 if shared.closed {
1977 return Err(Error::Closed);
1978 }
1979 for (prefix, id) in &self.entries {
1980 shared.reannounced(prefix, &route.hops);
1981 let stale = shared.withdrawn_through(prefix, &route.hops);
1982 let Some(entry) = shared.routes.entry_mut(prefix, *id) else {
1984 return Err(Error::Closed);
1985 };
1986 if entry.hops.iter().next() != route.hops.iter().next()
1989 && let Some(server) = &entry.server
1990 {
1991 drop(std::mem::take(&mut server.lock().served));
1992 }
1993 entry.hops = route.hops.clone();
1994 entry.stale = stale;
1995 entry.cost = route.cost;
1996 entry.via = route.via;
1997 entry.advertised = true;
1998 let claim = entry.claim.clone();
1999 shared.sync_route(prefix, &claim);
2000 shared.prune_withdrawn(prefix);
2001 }
2002 Ok(())
2003 }
2004
2005 fn withdraw(&self) {
2010 let mut shared = self.shared.lock();
2011 for (prefix, id) in &self.entries {
2012 let Some(entry) = shared.routes.entry_mut(prefix, *id) else {
2013 continue;
2014 };
2015 if !entry.advertised {
2016 continue;
2017 }
2018 entry.advertised = false;
2019 let claim = entry.claim.clone();
2020 shared.sync_route(prefix, &claim);
2021 }
2022 }
2023
2024 fn retract(&self, withdrawn: bool) {
2027 let mut shared = self.shared.lock();
2028 for (prefix, id) in &self.entries {
2029 let Some(entry) = shared.routes.remove(prefix, *id) else {
2030 continue;
2031 };
2032 if let Some(server) = &entry.server {
2035 let mut server = server.lock();
2036 server.closed = true;
2037 for producer in server.requests.drain_all() {
2038 if let Ok(mut request) = producer.write() {
2039 request.resolved.get_or_insert(Err(Error::Unroutable));
2040 }
2041 }
2042 }
2043 if withdrawn
2048 && let Some(&peer) = entry.hops.iter().last()
2049 && peer != Hop::UNKNOWN
2050 && !shared
2051 .routes
2052 .at(prefix)
2053 .any(|other| other.live() && other.hops.iter().last() == Some(&peer))
2054 {
2055 shared.withdrawn.entry(prefix.clone()).or_default().insert(peer);
2056 shared.restale(prefix);
2057 }
2058 shared.sync_route(&entry.prefix, &entry.claim);
2059 shared.prune_withdrawn(&entry.prefix);
2060 }
2061 }
2062}
2063
2064impl Drop for AnnounceProducer {
2065 fn drop(&mut self) {
2066 self.retract(false);
2067 }
2068}
2069
2070#[must_use = "poll the driver or the origin makes no progress"]
2085pub struct Driver {
2086 state: DriverState,
2087 timers: Clock,
2089 pool: cache::Pool,
2092}
2093
2094struct DriverState {
2096 set: TaskSet,
2098 shared: kio::Shared<OriginState>,
2101 done: bool,
2103}
2104
2105impl Driver {
2106 pub fn poll(&mut self, now: Instant, waiter: &kio::Waiter) -> Result<Option<Instant>, Error> {
2113 self.timers.advance(now);
2114 self.timers.register_driver(waiter);
2115 let result = self.state.poll(waiter);
2116 let gc = self.pool.gc(now);
2117 if result.is_ready() {
2118 return Err(Error::Closed);
2119 }
2120 Ok(self.timers.timeout().into_iter().chain(gc).min())
2121 }
2122}
2123
2124impl crate::time::Driver for Driver {
2125 fn poll(&mut self, now: Instant, waiter: &kio::Waiter) -> Result<Option<Instant>, Error> {
2126 self.poll(now, waiter)
2127 }
2128}
2129
2130impl DriverState {
2131 fn poll(&mut self, waiter: &kio::Waiter) -> Poll<()> {
2132 if !self.done {
2136 ready!(self.set.poll(waiter));
2137 self.done = true;
2138 }
2139 Poll::Ready(())
2140 }
2141
2142 fn teardown(&mut self) {
2146 drop(std::mem::replace(&mut self.set, TaskSet::owned()));
2149
2150 let (servers, cursors, fronts) = {
2155 let mut shared = self.shared.lock();
2156 shared.closed = true;
2157 shared.routes.poke_all();
2159 let servers: Vec<_> = shared
2160 .routes
2161 .entries()
2162 .filter_map(|entry| entry.server.clone())
2163 .collect();
2164 let cursors: Vec<_> = shared.cursors.values().map(|cursor| cursor.state.clone()).collect();
2165 let fronts: Vec<_> = shared.fronts.values().map(|front| front.request.clone()).collect();
2166 (servers, cursors, fronts)
2167 };
2168
2169 for producer in fronts {
2172 if let Ok(mut request) = producer.write() {
2173 request.resolved.get_or_insert(Err(Error::Dropped));
2174 }
2175 }
2176 for server in servers {
2180 let mut server = server.lock();
2181 server.closed = true;
2182 for producer in server.requests.drain_all() {
2183 if let Ok(mut request) = producer.write() {
2184 request.resolved.get_or_insert(Err(Error::Dropped));
2185 }
2186 }
2187 }
2188
2189 for state in cursors {
2192 if let Ok(mut state) = state.write() {
2193 state.ended = true;
2194 }
2195 }
2196 }
2197}
2198
2199impl Drop for DriverState {
2200 fn drop(&mut self) {
2201 self.teardown();
2202 }
2203}
2204
2205const TRACK_IDLE_LINGER: Duration = Duration::from_secs(30);
2219
2220struct WarmCopy {
2225 track: track::Producer,
2226 _dynamic: track::Dynamic,
2227 edge: Option<WarmGroup>,
2230}
2231
2232impl Drop for WarmCopy {
2233 fn drop(&mut self) {
2234 let _ = self.track.finish();
2235 }
2236}
2237
2238struct WarmGroup(group::Producer);
2245
2246impl Drop for WarmGroup {
2247 fn drop(&mut self) {
2248 if !self.0.is_finished() {
2249 let _ = self.0.clone().abort(Error::Cancel);
2250 }
2251 }
2252}
2253
2254struct HeldGroup(group::Producer);
2259
2260impl Drop for HeldGroup {
2261 fn drop(&mut self) {
2262 if !self.0.is_finished() {
2263 let _ = self.0.abort_if_last(Error::Cancel);
2264 }
2265 }
2266}
2267
2268fn warm_copy(source: &track::Consumer, head: Option<&WarmGroup>, held: Option<&group::Producer>) -> Option<WarmCopy> {
2276 let info = source.cached_info()?;
2277 let mut track = track::Producer::new(Arc::new(source.broadcast().clone()), source.name(), info);
2278 let head = head.map(|head| &head.0);
2279 let mut groups = source.cached_groups();
2280 if let Some(held) = held
2281 && !groups.iter().any(|(group, _)| group.sequence == held.sequence)
2282 {
2283 groups.push((held.clone(), true));
2284 }
2285 let latest = groups
2287 .iter()
2288 .filter(|(_, visible)| *visible)
2289 .map(|(group, _)| group.sequence)
2290 .max();
2291 let mut edge = None;
2292 let mut first = None;
2293
2294 if let Some(head) = head
2298 && !groups.iter().any(|(group, _)| group.sequence == head.sequence)
2299 {
2300 let is_latest = latest.is_none_or(|latest| head.sequence >= latest);
2301 if head.is_finished() {
2302 let _ = track.adopt_group(head.clone(), true);
2303 first = Some(head.sequence);
2304 if is_latest {
2305 edge = Some(WarmGroup(head.clone()));
2306 }
2307 } else if is_latest {
2308 edge = warm_rebuild(&track, head, None);
2309 first = edge.as_ref().map(|_| head.sequence);
2310 }
2311 }
2312
2313 for (group, visible) in groups {
2314 let finished = group.is_finished();
2315 let whole = group.live_first_frame() == Some(0);
2316 let is_latest = visible && Some(group.sequence) == latest;
2317 let warm = if finished && whole {
2318 let _ = track.adopt_group(group.clone(), visible);
2319 Some(WarmGroup(group))
2320 } else if (finished || is_latest) && (whole || head.is_some_and(|head| head.sequence == group.sequence)) {
2321 warm_rebuild(&track, &group, head)
2327 } else {
2328 None
2330 };
2331 if visible && let Some(warm) = &warm {
2332 first = Some(first.map_or(warm.0.sequence, |first: u64| first.min(warm.0.sequence)));
2333 }
2334 if is_latest {
2335 edge = warm;
2336 }
2337 }
2338 track.start_at(first).ok()?;
2342 let dynamic = track.dynamic();
2343 Some(WarmCopy {
2344 track,
2345 _dynamic: dynamic,
2346 edge,
2347 })
2348}
2349
2350fn warm_rebuild(track: &track::Producer, live: &group::Producer, head: Option<&group::Producer>) -> Option<WarmGroup> {
2353 let mut start = live.live_first_frame()? as u64;
2355 let mut tail = live.consume();
2356 tail.start_at(start);
2357 let mut frames = Vec::new();
2358 if start > 0
2359 && let Some(head) = head.filter(|head| head.sequence == live.sequence)
2360 && let Some(head_start) = head.live_first_frame()
2361 {
2362 let mut head = head.consume();
2363 head.start_at(head_start as u64);
2364 while head.index() < start {
2365 match head.poll_read_frame(&kio::Waiter::noop()) {
2366 Poll::Ready(Ok(Some(frame))) => frames.push(frame),
2367 _ => break,
2368 }
2369 }
2370 match head.index() == start {
2371 true => start = head_start as u64,
2372 false => frames.clear(),
2374 }
2375 }
2376 while let Poll::Ready(Ok(Some(frame))) = tail.poll_read_frame(&kio::Waiter::noop()) {
2377 frames.push(frame);
2378 }
2379 if frames.is_empty() {
2380 return None;
2381 }
2382
2383 let rebuilt = WarmGroup(
2385 track
2386 .create_group(group::Info {
2387 sequence: live.sequence,
2388 })
2389 .ok()?,
2390 );
2391 let mut writer = rebuilt.0.clone();
2392 if start > 0 {
2393 writer.start_at(start).ok()?;
2394 }
2395 for frame in frames {
2396 writer.write_frame(frame.timestamp, frame.payload).ok()?;
2397 }
2398 if live.is_finished() {
2399 writer.finish().ok()?;
2400 }
2401 Some(rebuilt)
2402}
2403
2404struct FrontTask {
2406 shared: kio::Shared<OriginState>,
2408 broadcast: broadcast::Producer,
2410 path: PathOwned,
2412 horizon: Horizon,
2414 watch: Watch,
2416 request: kio::Producer<PendingBroadcast>,
2418 pin: kio::Lock<Pin>,
2420 timers: Clock,
2421}
2422
2423struct TrackIo {
2426 resume: super::resume::Producer,
2427 staged: Option<(u64, track::Consumer)>,
2429 query: Option<(u64, track::Consumer, track::Querying)>,
2431 copy: Option<(u64, track::Consumer)>,
2433 edge: Option<track::Position>,
2438 warm: Option<WarmCopy>,
2441 head: Option<WarmGroup>,
2444 held: Option<HeldGroup>,
2449 dead: Option<track::Consumer>,
2452 used: bool,
2454}
2455
2456impl TrackIo {
2457 fn end(&mut self) {
2460 self.staged = None;
2461 self.query = None;
2462 self.copy = None;
2463 self.warm = None;
2464 self.head = None;
2465 self.held = None;
2466 self.dead = None;
2467 }
2468
2469 fn bury(&mut self) {
2472 let Some((_, copy)) = self.copy.take() else { return };
2473 let after = self.held.as_ref().map(|held| held.0.sequence);
2474 if let Poll::Ready(group) = copy.poll_latest_group(after, &kio::Waiter::noop()) {
2475 self.held = Some(HeldGroup(group));
2476 }
2477 self.dead = Some(copy);
2478 }
2479
2480 fn retire(&mut self, copy: &track::Consumer) -> Result<(), Error> {
2485 let Some(held) = self.held.take() else {
2486 return Ok(());
2487 };
2488 if held.0.is_finished() {
2489 return Ok(());
2490 }
2491 let Some(warm) = warm_copy(copy, self.head.as_ref(), Some(&held.0)) else {
2492 return Ok(());
2493 };
2494 self.head = None;
2495 self.resume.park(&warm.track)?;
2496 self.warm = Some(warm);
2497 Ok(())
2498 }
2499}
2500
2501async fn run_front(task: FrontTask) {
2505 let FrontTask {
2506 shared,
2507 broadcast,
2508 path,
2509 horizon,
2510 watch,
2511 request,
2512 pin,
2513 timers,
2514 } = task;
2515
2516 enum Step {
2518 Assigned(Arc<str>, super::resume::Producer),
2519 Resolved(u64, Result<broadcast::Consumer, Error>),
2520 SourceClosed(u64),
2521 Info(Arc<str>, u64, Result<track::Info, Error>),
2522 Ended(Arc<str>, u64, Result<(), Error>),
2523 Held(Arc<str>, group::Producer),
2524 Demand(Arc<str>),
2525 Deadline,
2526 Table,
2527 }
2528
2529 let mut front = Front::new(TRACK_IDLE_LINGER);
2530 let mut sources: HashMap<u64, broadcast::Consumer> = HashMap::new();
2531 let mut next_source = 0u64;
2532 let mut upstream: Option<(u64, kio::Consumer<PendingBroadcast>)> = None;
2534 let mut tracks: HashMap<Arc<str>, TrackIo> = HashMap::new();
2535 let mut deadline = crate::runtime::Deadline::new(&timers);
2536 let mut seen = 0;
2538 let mut events: VecDeque<Event> = VecDeque::new();
2539
2540 let select = |front: &mut Front, sources: &HashMap<u64, broadcast::Consumer>, seen: &mut u64| -> Event {
2543 let table = shared.read();
2544 if table.closed {
2545 return Event::Closed;
2546 }
2547 *seen = watch.seen();
2549 front.retain_routes(|route| table.routes.covers(&path.as_path(), route));
2550 let best = table
2551 .best_route(&path.as_path(), horizon, front.pin(), front.refused_routes())
2552 .map(|entry| Candidate {
2553 route: entry.id,
2554 first: entry.hops.iter().next().copied(),
2555 local: entry.local,
2556 });
2557 let serving_closing = front
2558 .serving()
2559 .and_then(|id| sources.get(&id))
2560 .is_some_and(|source| source.is_closing());
2561 Event::Selected { best, serving_closing }
2562 };
2563
2564 events.push_back(select(&mut front, &sources, &mut seen));
2565
2566 loop {
2567 while let Some(event) = events.pop_front() {
2568 for action in front.step(event) {
2569 match action {
2570 Action::Reselect => events.push_back(select(&mut front, &sources, &mut seen)),
2571 Action::Request { route } => {
2572 let found = {
2574 let table = shared.read();
2575 table
2576 .routes
2577 .covering(&path.as_path())
2578 .find(|entry| entry.id == route && entry.live())
2579 .map(|entry| {
2580 (
2581 Candidate {
2582 route,
2583 first: entry.hops.iter().next().copied(),
2584 local: entry.local,
2585 },
2586 entry.source.clone(),
2587 entry.server.clone(),
2588 )
2589 })
2590 };
2591 let Some((candidate, source, server)) = found else {
2592 events.push_back(Event::Resolved {
2593 route,
2594 result: Err(Refusal {
2595 err: Error::Unroutable,
2596 standing: false,
2597 }),
2598 });
2599 continue;
2600 };
2601 front.identify(candidate);
2602 *pin.lock() = front.pin();
2603 if let Some(source) = source {
2604 let id = next_source;
2605 next_source += 1;
2606 sources.insert(id, source);
2607 events.push_back(Event::Resolved { route, result: Ok(id) });
2608 continue;
2609 }
2610 let Some(server) = server else {
2611 events.push_back(Event::Resolved {
2612 route,
2613 result: Err(Refusal {
2614 err: Error::Unroutable,
2615 standing: true,
2616 }),
2617 });
2618 continue;
2619 };
2620 let mut serve = server.lock();
2621 if serve.closed {
2622 drop(serve);
2625 events.push_back(Event::Resolved {
2626 route,
2627 result: Err(Refusal {
2628 err: Error::Unroutable,
2629 standing: true,
2630 }),
2631 });
2632 continue;
2633 }
2634 if let Some(weak) = serve.served.get(&path) {
2637 drop(serve);
2638 let id = next_source;
2639 next_source += 1;
2640 sources.insert(id, weak.consume());
2641 events.push_back(Event::Resolved { route, result: Ok(id) });
2642 continue;
2643 }
2644 let pending = match serve.requests.join(&path) {
2645 Some(producer) => producer.consume(),
2646 None => {
2647 let producer = kio::Producer::<PendingBroadcast>::default();
2648 let consumer = producer.consume();
2649 match serve.requests.insert(path.clone(), producer) {
2650 Ok(()) => consumer,
2651 Err(_) => {
2654 drop(serve);
2655 events.push_back(Event::Resolved {
2656 route,
2657 result: Err(Refusal {
2658 err: Error::Unroutable,
2659 standing: true,
2660 }),
2661 });
2662 continue;
2663 }
2664 }
2665 }
2666 };
2667 upstream = Some((route, pending));
2668 }
2669 Action::Detach { source } => {
2670 sources.remove(&source);
2671 for io in tracks.values_mut() {
2674 if io.copy.as_ref().is_some_and(|(s, _)| *s == source) {
2675 io.bury();
2676 }
2677 if io.query.as_ref().is_some_and(|(s, ..)| *s == source) {
2678 io.query = None;
2679 }
2680 if io.staged.as_ref().is_some_and(|(s, _)| *s == source) {
2681 io.staged = None;
2682 }
2683 }
2684 }
2685 Action::Resolve => {
2686 if let Ok(mut pending) = request.write() {
2687 pending.resolved.get_or_insert(Ok(broadcast.consume()));
2688 }
2689 }
2690 Action::Query { track: name, source } => {
2691 let Some(io) = tracks.get_mut(&name) else { continue };
2692 let closing = sources.get(&source).is_some_and(|s| s.is_closing());
2693 match sources.get(&source).map(|s| s.track(&name)) {
2694 Some(Ok(copy)) => {
2695 let query = copy.query().into_inner();
2698 io.query = Some((source, copy, query));
2699 }
2700 Some(Err(err)) => events.push_back(Event::TrackInfo {
2701 track: name,
2702 source,
2703 closing,
2704 result: Err(err),
2705 }),
2706 None => {}
2707 }
2708 }
2709 Action::Splice { track: name, source } => {
2710 let Some(io) = tracks.get_mut(&name) else { continue };
2711 let Some((staged, copy)) = io.staged.take() else {
2712 continue;
2713 };
2714 if staged != source {
2715 continue;
2716 }
2717 let outgoing = io.copy.take().map(|(_, copy)| copy).or_else(|| io.dead.take());
2718 let retired = match outgoing {
2719 Some(outgoing) => io.retire(&outgoing),
2720 None => Ok(()),
2721 };
2722 if let Err(err) = retired.and_then(|()| io.resume.takeover(©)) {
2723 let _ = io.resume.abort(err);
2727 tracks.remove(&name);
2728 continue;
2729 }
2730 if let Some(head) = io.warm.take().and_then(|mut warm| warm.edge.take()) {
2731 io.head = Some(head);
2732 }
2733 io.edge = io.resume.resume_position();
2736 io.held = None;
2737 io.copy = Some((source, copy));
2738 }
2739 Action::Park { track: name } => {
2740 let Some(io) = tracks.get_mut(&name) else { continue };
2741 let Some((_, copy)) = io.copy.take() else { continue };
2742 let warm = warm_copy(©, io.head.as_ref(), io.held.as_ref().map(|held| &held.0));
2746 drop(copy);
2747 io.head = None;
2748 io.held = None;
2749 io.dead = None;
2750 let parked = match &warm {
2751 Some(warm) => io.resume.park(&warm.track),
2752 None => io.resume.release(),
2753 };
2754 if parked.is_err() {
2755 tracks.remove(&name);
2756 continue;
2757 }
2758 io.warm = warm;
2759 }
2760 Action::Forget { track: name } => {
2761 if let Some(io) = tracks.get_mut(&name)
2766 && !broadcast.forget_spliced(&name, &io.resume)
2767 {
2768 if !io.used {
2769 io.used = true;
2770 events.push_back(Event::Used { track: name });
2771 }
2772 continue;
2773 }
2774 tracks.remove(&name);
2775 events.push_back(Event::Forgotten { track: name });
2776 }
2777 Action::Finish { track: name } => {
2778 if let Some(io) = tracks.get_mut(&name) {
2779 io.end();
2780 let _ = io.resume.finish();
2781 }
2782 }
2783 Action::Abort { track: name, err } => {
2784 if let Some(io) = tracks.get_mut(&name) {
2785 tracing::debug!(name = %name, %err, "aborting track");
2786 io.end();
2787 let _ = io.resume.abort(err);
2788 }
2789 }
2790 Action::Arm { at } => deadline.set(at),
2791 Action::End { err } => {
2792 if let Ok(mut pending) = request.write() {
2793 pending.resolved.get_or_insert(Err(err.clone()));
2794 }
2795 broadcast.close();
2802 broadcast.release_spliced(err.clone());
2803 for (_, mut io) in tracks.drain() {
2804 let waiting = io.staged.take().map(|(_, copy)| copy);
2808 let waiting = waiting.or_else(|| io.query.take().map(|(_, copy, _)| copy));
2809 if let Some(copy) = waiting
2810 && io.resume.is_used()
2811 {
2812 if io.resume.takeover(©).is_err() {
2813 continue;
2814 }
2815 io.warm = None;
2816 }
2817 if !io.resume.is_used() || !io.resume.is_spliced() || io.warm.is_some() {
2819 let _ = io.resume.abort(err.clone());
2820 }
2821 }
2822 return;
2823 }
2824 }
2825 }
2826 }
2827
2828 let step = kio::wait(|waiter| {
2829 if let Poll::Ready((name, resume)) = broadcast.poll_spliced_assigned(waiter) {
2830 return Poll::Ready(Step::Assigned(name, resume));
2831 }
2832 if let Some((route, pending)) = &upstream
2833 && let Poll::Ready(result) = pending.poll(waiter, |p| match &p.resolved {
2834 Some(result) => Poll::Ready(result.clone()),
2835 None => Poll::Pending,
2836 }) {
2837 return Poll::Ready(Step::Resolved(
2838 *route,
2839 match result {
2840 Ok(resolved) => resolved,
2841 Err(_closed) => Err(Error::Unroutable),
2844 },
2845 ));
2846 }
2847 if let Some(id) = front.serving()
2848 && let Some(source) = sources.get(&id)
2849 && source.poll_closed(waiter).is_ready()
2850 {
2851 return Poll::Ready(Step::SourceClosed(id));
2852 }
2853 for (name, io) in &tracks {
2854 if let Some((source, _, query)) = &io.query
2855 && let Poll::Ready(result) = query.poll(waiter)
2856 {
2857 return Poll::Ready(Step::Info(name.clone(), *source, result));
2858 }
2859 if let Some((source, copy)) = &io.copy
2860 && let Poll::Ready(result) = copy.poll_complete(waiter)
2861 {
2862 return Poll::Ready(Step::Ended(name.clone(), *source, result));
2863 }
2864 if let Some((_, copy)) = &io.copy
2865 && let Poll::Ready(group) =
2866 copy.poll_latest_group(io.held.as_ref().map(|held| held.0.sequence), waiter)
2867 {
2868 return Poll::Ready(Step::Held(name.clone(), group));
2869 }
2870 let edge = match io.used {
2872 true => io.resume.poll_unused(waiter),
2873 false => io.resume.poll_used(waiter),
2874 };
2875 if edge.is_ready() {
2876 return Poll::Ready(Step::Demand(name.clone()));
2877 }
2878 }
2879 if deadline.poll(waiter).is_ready() {
2880 return Poll::Ready(Step::Deadline);
2881 }
2882 watch.poll_changed(waiter, seen).map(|()| Step::Table)
2883 })
2884 .await;
2885
2886 let event = match step {
2887 Step::Assigned(name, resume) => {
2888 tracks.insert(
2889 name.clone(),
2890 TrackIo {
2891 resume,
2892 staged: None,
2893 query: None,
2894 copy: None,
2895 edge: None,
2896 warm: None,
2897 head: None,
2898 held: None,
2899 dead: None,
2900 used: false,
2901 },
2902 );
2903 Event::TrackAssigned {
2904 track: name,
2905 now: timers.now(),
2906 }
2907 }
2908 Step::Resolved(route, result) => {
2909 upstream = None;
2910 match result {
2911 Ok(source) => {
2912 let id = next_source;
2913 next_source += 1;
2914 sources.insert(id, source);
2915 Event::Resolved { route, result: Ok(id) }
2916 }
2917 Err(err) => {
2918 let standing =
2922 !matches!(err, Error::Unroutable) || shared.read().routes.covers(&path.as_path(), route);
2923 Event::Resolved {
2924 route,
2925 result: Err(Refusal { err, standing }),
2926 }
2927 }
2928 }
2929 }
2930 Step::SourceClosed(source) => Event::SourceClosed { source },
2931 Step::Info(name, source, result) => {
2932 let closing = sources.get(&source).is_some_and(|s| s.is_closing());
2933 let Some(io) = tracks.get_mut(&name) else { continue };
2934 let Some((_, copy, _)) = io.query.take() else { continue };
2935 let result = match result {
2938 Ok(info) => match copy.poll_complete(&kio::Waiter::noop()) {
2939 Poll::Ready(Err(err)) => Err(err),
2940 _ => Ok(info),
2941 },
2942 Err(err) => Err(err),
2943 };
2944 if result.is_ok() && io.used {
2947 io.staged = Some((source, copy));
2948 }
2949 Event::TrackInfo {
2950 track: name,
2951 source,
2952 closing,
2953 result,
2954 }
2955 }
2956 Step::Ended(name, source, result) => {
2957 let closing = sources.get(&source).is_some_and(|s| s.is_closing());
2958 let Some(io) = tracks.get_mut(&name) else { continue };
2959 io.bury();
2960 let delivered = io.resume.resume_position() != io.edge;
2961 Event::TrackEnded {
2962 track: name,
2963 source,
2964 closing,
2965 result,
2966 delivered,
2967 }
2968 }
2969 Step::Held(name, group) => {
2970 if let Some(io) = tracks.get_mut(&name) {
2971 io.held = Some(HeldGroup(group));
2972 }
2973 continue;
2974 }
2975 Step::Demand(name) => {
2976 let Some(io) = tracks.get_mut(&name) else { continue };
2977 io.used = io.resume.is_used();
2978 if !io.used {
2979 io.query = None;
2982 io.staged = None;
2983 }
2984 match io.used {
2985 true => Event::Used { track: name },
2986 false => Event::Unused {
2987 track: name,
2988 now: timers.now(),
2989 },
2990 }
2991 }
2992 Step::Deadline => {
2993 deadline.set(None);
2996 Event::Deadline { now: timers.now() }
2997 }
2998 Step::Table => select(&mut front, &sources, &mut seen),
2999 };
3000 events.push_back(event);
3001 }
3002}
3003
3004#[derive(Default)]
3009struct RouteTable {
3010 root: RouteNode,
3011}
3012
3013#[derive(Default)]
3016struct RouteNode {
3017 entries: Vec<RouteEntry>,
3019 cursors: Vec<ConsumerId>,
3021 cursors_below: usize,
3024 watches: Vec<(u64, kio::Producer<Watched>)>,
3027 watches_below: usize,
3030 children: HashMap<String, RouteNode>,
3031}
3032
3033#[derive(Default)]
3036struct Watched {
3037 generation: u64,
3038}
3039
3040struct Watch {
3046 shared: kio::Shared<OriginState>,
3047 path: PathOwned,
3048 id: u64,
3049 signal: kio::Consumer<Watched>,
3050}
3051
3052impl Watch {
3053 fn seen(&self) -> u64 {
3057 self.signal.read().generation
3058 }
3059
3060 fn poll_changed(&self, waiter: &kio::Waiter, seen: u64) -> Poll<()> {
3062 self.signal
3063 .poll(waiter, |watched| match watched.generation != seen {
3064 true => Poll::Ready(()),
3065 false => Poll::Pending,
3066 })
3067 .map(|_| ())
3068 }
3069}
3070
3071impl Drop for Watch {
3072 fn drop(&mut self) {
3073 self.shared.lock().routes.remove_watch(&self.path, self.id);
3074 }
3075}
3076
3077#[derive(Clone, Copy)]
3079struct Below {
3080 cursors: usize,
3081 watches: usize,
3082}
3083
3084impl Below {
3085 const NONE: Self = Self { cursors: 0, watches: 0 };
3086 const CURSOR: Self = Self { cursors: 1, watches: 0 };
3087 const WATCH: Self = Self { cursors: 0, watches: 1 };
3088}
3089
3090impl RouteNode {
3091 fn is_empty(&self) -> bool {
3093 self.entries.is_empty() && self.cursors.is_empty() && self.watches.is_empty() && self.children.is_empty()
3094 }
3095
3096 fn find<'a>(&self, mut parts: impl Iterator<Item = &'a str>) -> Option<&Self> {
3098 match parts.next() {
3099 None => Some(self),
3100 Some(part) => self.children.get(part)?.find(parts),
3101 }
3102 }
3103
3104 fn reach<'a>(&mut self, mut parts: impl Iterator<Item = &'a str>, below: Below) -> &mut Self {
3107 self.cursors_below += below.cursors;
3108 self.watches_below += below.watches;
3109 match parts.next() {
3110 None => self,
3111 Some(part) => self.children.entry(part.to_string()).or_default().reach(parts, below),
3112 }
3113 }
3114
3115 fn edit<'a, R>(
3119 &mut self,
3120 mut parts: impl Iterator<Item = &'a str>,
3121 below: Below,
3122 f: impl FnOnce(&mut Self) -> R,
3123 ) -> Option<R> {
3124 let result = match parts.next() {
3125 None => f(self),
3126 Some(part) => {
3127 let child = self.children.get_mut(part)?;
3128 let result = child.edit(parts, below, f)?;
3129 if child.is_empty() {
3130 self.children.remove(part);
3131 }
3132 result
3133 }
3134 };
3135 self.cursors_below -= below.cursors;
3136 self.watches_below -= below.watches;
3137 Some(result)
3138 }
3139
3140 fn poke(&self) {
3142 for (_, watch) in &self.watches {
3143 if let Ok(mut watched) = watch.write() {
3144 watched.generation += 1;
3145 }
3146 }
3147 }
3148
3149 fn poke_below(&self) {
3152 if self.watches_below == 0 {
3153 return;
3154 }
3155 self.poke();
3156 for child in self.children.values() {
3157 child.poke_below();
3158 }
3159 }
3160
3161 fn walk<'a>(&'a self, visit: &mut impl FnMut(&'a Self)) {
3163 visit(self);
3164 for child in self.children.values() {
3165 child.walk(visit);
3166 }
3167 }
3168
3169 fn collect_cursors(&self, out: &mut Vec<ConsumerId>) {
3171 if self.cursors_below == 0 {
3172 return;
3173 }
3174 out.extend(&self.cursors);
3175 for child in self.children.values() {
3176 child.collect_cursors(out);
3177 }
3178 }
3179}
3180
3181impl RouteTable {
3182 fn split(&self, path: &Path) -> (Vec<&RouteNode>, Option<&RouteNode>) {
3185 let mut above = Vec::new();
3186 let mut node = &self.root;
3187 for part in path.parts() {
3188 above.push(node);
3189 match node.children.get(part) {
3190 Some(child) => node = child,
3191 None => return (above, None),
3192 }
3193 }
3194 (above, Some(node))
3195 }
3196
3197 fn covering(&self, path: &Path) -> impl Iterator<Item = &RouteEntry> {
3199 let (above, at) = self.split(path);
3200 above.into_iter().chain(at).flat_map(|node| node.entries.iter())
3201 }
3202
3203 fn covers(&self, path: &Path, id: u64) -> bool {
3205 self.covering(path).any(|entry| entry.id == id && entry.live())
3206 }
3207
3208 fn at(&self, prefix: &Path) -> impl Iterator<Item = &RouteEntry> {
3210 self.root
3211 .find(prefix.parts())
3212 .into_iter()
3213 .flat_map(|node| node.entries.iter())
3214 }
3215
3216 fn at_mut(&mut self, prefix: &Path) -> impl Iterator<Item = &mut RouteEntry> {
3218 let mut node = Some(&mut self.root);
3219 for part in prefix.parts() {
3220 node = node.and_then(|node| node.children.get_mut(part));
3221 }
3222 node.into_iter().flat_map(|node| node.entries.iter_mut())
3223 }
3224
3225 fn entries(&self) -> impl Iterator<Item = &RouteEntry> {
3227 let mut nodes = Vec::new();
3228 self.root.walk(&mut |node| nodes.push(node));
3229 nodes.into_iter().flat_map(|node| node.entries.iter())
3230 }
3231
3232 fn insert(&mut self, entry: RouteEntry) {
3234 let node = self.root.reach(entry.prefix.parts(), Below::NONE);
3235 node.entries.push(entry);
3236 }
3237
3238 fn entry_mut(&mut self, prefix: &Path, id: u64) -> Option<&mut RouteEntry> {
3240 let mut node = &mut self.root;
3241 for part in prefix.parts() {
3242 node = node.children.get_mut(part)?;
3243 }
3244 node.entries.iter_mut().find(|entry| entry.id == id)
3245 }
3246
3247 fn remove(&mut self, prefix: &Path, id: u64) -> Option<RouteEntry> {
3249 self.root
3250 .edit(prefix.parts(), Below::NONE, |node| {
3251 let index = node.entries.iter().position(|entry| entry.id == id)?;
3252 Some(node.entries.swap_remove(index))
3253 })
3254 .flatten()
3255 }
3256
3257 fn add_cursor(&mut self, head: &Path, id: ConsumerId) {
3259 self.root.reach(head.parts(), Below::CURSOR).cursors.push(id);
3260 }
3261
3262 fn remove_cursor(&mut self, head: &Path, id: ConsumerId) {
3265 self.root.edit(head.parts(), Below::CURSOR, |node| {
3266 node.cursors.retain(|cursor| *cursor != id)
3267 });
3268 }
3269
3270 fn add_watch(&mut self, path: &Path, id: u64) -> kio::Consumer<Watched> {
3272 let producer = kio::Producer::<Watched>::default();
3273 let consumer = producer.consume();
3274 self.root.reach(path.parts(), Below::WATCH).watches.push((id, producer));
3275 consumer
3276 }
3277
3278 fn remove_watch(&mut self, path: &Path, id: u64) {
3281 self.root.edit(path.parts(), Below::WATCH, |node| {
3282 node.watches.retain(|(watch, _)| *watch != id)
3283 });
3284 }
3285
3286 fn poke_below(&self, prefix: &Path) {
3288 if let (_, Some(node)) = self.split(prefix) {
3289 node.poke_below();
3290 }
3291 }
3292
3293 fn poke_all(&self) {
3295 self.root.walk(&mut |node| node.poke());
3296 }
3297
3298 fn cursors_touching(&self, prefix: &Path) -> Vec<ConsumerId> {
3302 let (above, at) = self.split(prefix);
3303 let mut cursors: Vec<ConsumerId> = above.iter().flat_map(|node| node.cursors.iter().copied()).collect();
3304 if let Some(node) = at {
3305 node.collect_cursors(&mut cursors);
3306 }
3307 cursors.sort_unstable();
3309 cursors.dedup();
3310 cursors
3311 }
3312}
3313
3314struct OriginState {
3321 update_hold: Duration,
3324
3325 routes: RouteTable,
3328 next_route: u64,
3329 next_watch: u64,
3330
3331 cursors: HashMap<ConsumerId, TableCursor>,
3335
3336 fronts: WeakCache<FrontKey, RemoteFront>,
3342
3343 withdrawn: HashMap<PathOwned, HashSet<Hop>>,
3346
3347 closed: bool,
3350}
3351
3352impl OriginState {
3353 fn new(update_hold: Duration) -> Self {
3354 Self {
3355 update_hold,
3356 routes: RouteTable::default(),
3357 next_route: 0,
3358 next_watch: 0,
3359 cursors: HashMap::new(),
3360 fronts: WeakCache::default(),
3361 withdrawn: HashMap::new(),
3362 closed: false,
3363 }
3364 }
3365
3366 fn withdrawn_through(&self, prefix: &Path, hops: &Hops) -> bool {
3368 if self.withdrawn.is_empty() {
3369 return false;
3370 }
3371 self.withdrawn
3372 .get(prefix)
3373 .is_some_and(|peers| hops.iter().any(|hop| peers.contains(hop)))
3374 }
3375
3376 fn restale(&mut self, prefix: &Path) {
3378 let peers = self.withdrawn.get(prefix);
3379 let mut changed = None;
3380 for entry in self.routes.at_mut(prefix) {
3381 let stale = peers.is_some_and(|peers| entry.hops.iter().any(|hop| peers.contains(hop)));
3382 if entry.stale != stale {
3383 entry.stale = stale;
3384 changed = Some(entry.claim.clone());
3385 }
3386 }
3387 if let Some(claim) = changed {
3388 self.sync_route(prefix, &claim);
3389 }
3390 }
3391
3392 fn reannounced(&mut self, prefix: &PathOwned, hops: &Hops) {
3396 if let Some(sender) = hops.iter().last()
3397 && self.withdrawn.get_mut(prefix).is_some_and(|peers| peers.remove(sender))
3398 {
3399 self.restale(prefix);
3400 self.prune_withdrawn(prefix);
3401 }
3402 }
3403
3404 fn prune_withdrawn(&mut self, prefix: &PathOwned) {
3406 if self.withdrawn.is_empty() {
3407 return;
3408 }
3409 let Some(peers) = self.withdrawn.get_mut(prefix) else {
3410 return;
3411 };
3412 if peers.len() <= 1 {
3413 peers.retain(|peer| self.routes.at(prefix).any(|entry| entry.hops.contains(peer)));
3415 } else {
3416 let mut unreferenced = peers.clone();
3417 for entry in self.routes.at(prefix) {
3418 if unreferenced.is_empty() {
3419 break;
3420 }
3421 for hop in entry.hops.iter() {
3422 unreferenced.remove(hop);
3423 }
3424 }
3425 peers.retain(|peer| !unreferenced.contains(peer));
3426 }
3427 if peers.is_empty() {
3428 self.withdrawn.remove(prefix);
3429 }
3430 }
3431
3432 fn sync_route(&mut self, prefix: &Path, claim: &Pattern) {
3437 let routes = &self.routes;
3439 let mut candidates = None;
3440 for id in routes.cursors_touching(prefix) {
3441 let Some(cursor) = self.cursors.get_mut(&id) else {
3442 continue;
3443 };
3444 if let Some(presented) = cursor.presented(prefix, claim) {
3445 if presented.is_empty() {
3446 Self::sync_cursor(routes, cursor, &presented);
3448 } else {
3449 let candidates = candidates.get_or_insert_with(|| {
3452 let mut entries: Vec<_> = routes
3453 .at(prefix)
3454 .map(|entry| (route_order(prefix, entry), entry))
3455 .collect();
3456 entries.sort_unstable_by_key(|(order, _)| *order);
3457 entries
3458 });
3459 let best = candidates
3460 .iter()
3461 .map(|(_, entry)| *entry)
3462 .find(|entry| cursor.visible(entry));
3463 cursor.update(&presented, best);
3464 }
3465 }
3466 }
3467 routes.poke_below(prefix);
3469 }
3470
3471 fn watch(&mut self, shared: &kio::Shared<OriginState>, path: &Path) -> Watch {
3473 let id = self.next_watch;
3474 self.next_watch += 1;
3475 let signal = self.routes.add_watch(path, id);
3476 Watch {
3477 shared: shared.clone(),
3478 path: path.to_owned(),
3479 id,
3480 signal,
3481 }
3482 }
3483
3484 fn sync_cursor(routes: &RouteTable, cursor: &mut TableCursor, presented: &PathOwned) {
3487 let candidates: Vec<&RouteEntry> = match presented.is_empty() {
3493 true => routes
3494 .covering(&cursor.root)
3495 .filter(|entry| cursor.visible(entry))
3496 .collect(),
3497 false => {
3498 let absolute = cursor.root.join(presented);
3499 routes.at(&absolute).filter(|entry| cursor.visible(entry)).collect()
3500 }
3501 };
3502 let most = candidates.iter().map(|entry| entry.prefix.len()).max();
3503 let best = most.and_then(|most| {
3504 candidates
3505 .into_iter()
3506 .filter(|entry| entry.prefix.len() == most)
3507 .min_by_key(|entry| route_order(&entry.prefix, entry))
3508 });
3509
3510 cursor.update(presented, best);
3511 }
3512
3513 fn register_cursor(&mut self, id: ConsumerId, mut cursor: TableCursor) {
3515 let mut presented: BTreeSet<PathOwned> = BTreeSet::new();
3518 for head in &cursor.heads {
3519 let (above, at) = self.routes.split(head);
3520 let mut nodes = above;
3521 if let Some(node) = at {
3522 node.walk(&mut |node| nodes.push(node));
3523 }
3524 for entry in nodes.into_iter().flat_map(|node| node.entries.iter()) {
3525 if let Some(p) = cursor.presented(&entry.prefix, &entry.claim) {
3526 presented.insert(p);
3527 }
3528 }
3529 }
3530 for p in &presented {
3531 Self::sync_cursor(&self.routes, &mut cursor, p);
3532 }
3533 for head in &cursor.heads {
3534 self.routes.add_cursor(head, id);
3535 }
3536 self.cursors.insert(id, cursor);
3537 }
3538
3539 fn best_route(&self, path: &Path, horizon: Horizon, pin: Pin, refused: &HashSet<u64>) -> Option<&RouteEntry> {
3554 let (above, at) = self.routes.split(path);
3558 let mut best = None;
3559 for node in above.into_iter().chain(at) {
3560 let mut candidates = node
3561 .entries
3562 .iter()
3563 .filter(|entry| entry.live())
3564 .filter(|entry| entry.scope.matches(path.as_str()))
3565 .filter(|entry| horizon.admits(entry))
3566 .filter(|entry| entry.qualifies(pin))
3567 .filter(|entry| !refused.contains(&entry.id))
3568 .peekable();
3569 if candidates.peek().is_some() {
3570 best = candidates
3571 .filter(|entry| entry.serves(path))
3572 .min_by_key(|entry| route_order(&entry.prefix, entry));
3573 }
3574 }
3575 best
3576 }
3577}
3578
3579#[derive(Default)]
3586struct PendingBroadcast {
3587 resolved: Option<Result<broadcast::Consumer, Error>>,
3588}
3589
3590#[must_use = "dropping an origin::Dynamic retracts the route"]
3605pub struct Dynamic {
3606 announcement: AnnounceProducer,
3608 state: kio::Shared<ServeState>,
3609 _keepalive: Keepalive,
3613}
3614
3615impl Dynamic {
3616 pub(crate) fn withdrawn(self) {
3619 self.announcement.retract(true);
3620 }
3621
3622 pub fn update(&self, route: Route) -> Result<(), Error> {
3632 self.announcement.update(route)
3633 }
3634
3635 pub fn poll_requested_broadcast(&self, waiter: &kio::Waiter) -> Poll<Result<Request, Error>> {
3640 let mut state = ready!(self.state.poll(waiter, |state| {
3641 if state.closed || state.requests.has_queued() {
3642 Poll::Ready(())
3643 } else {
3644 Poll::Pending
3645 }
3646 }));
3647
3648 if state.closed {
3650 return Poll::Ready(Err(Error::Closed));
3651 }
3652
3653 let path = state.requests.pop().expect("predicate guaranteed a request");
3654 let producer = state.requests.get(&path).expect("popped key must be pending").clone();
3660 Poll::Ready(Ok(Request {
3661 path,
3662 producer,
3663 home: self.state.clone(),
3664 }))
3665 }
3666
3667 pub async fn requested_broadcast(&self) -> Result<Request, Error> {
3673 kio::wait(|waiter| self.poll_requested_broadcast(waiter)).await
3674 }
3675}
3676
3677impl ServeState {
3678 fn resolve(
3689 shared: &kio::Shared<Self>,
3690 path: &PathOwned,
3691 producer: &kio::Producer<PendingBroadcast>,
3692 result: Result<broadcast::Consumer, Error>,
3693 ) {
3694 let mut state = shared.lock();
3695 if state.closed {
3696 return;
3697 }
3698 let resolved = match result {
3699 Ok(broadcast) => {
3700 let existing = state.served.insert(path.clone(), broadcast.weak());
3704 Ok(existing.map(|weak| weak.consume()).unwrap_or(broadcast))
3705 }
3706 Err(err) => Err(err),
3707 };
3708 state.requests.remove_if(path, |p| p.same_channel(producer));
3709 if let Ok(mut pending) = producer.write() {
3710 pending.resolved.get_or_insert(resolved);
3711 drop(state);
3712 }
3713 }
3714
3715 fn forget(shared: &kio::Shared<Self>, path: &PathOwned, producer: &kio::Producer<PendingBroadcast>) {
3717 shared.lock().requests.remove_if(path, |p| p.same_channel(producer));
3718 }
3719}
3720
3721pub struct Request {
3728 path: PathOwned,
3730
3731 producer: kio::Producer<PendingBroadcast>,
3734
3735 home: kio::Shared<ServeState>,
3738}
3739
3740impl Request {
3741 pub fn path(&self) -> &Path<'_> {
3743 &self.path
3744 }
3745
3746 pub fn accept(self, broadcast: impl Consume<broadcast::Consumer>) {
3752 let broadcast = broadcast.consume();
3753 ServeState::resolve(&self.home, &self.path, &self.producer, Ok(broadcast));
3754 }
3756
3757 pub fn reject(self, err: Error) {
3759 ServeState::resolve(&self.home, &self.path, &self.producer, Err(err));
3760 }
3761}
3762
3763impl Drop for Request {
3764 fn drop(&mut self) {
3765 ServeState::forget(&self.home, &self.path, &self.producer);
3773 }
3774}
3775
3776pub struct Requesting {
3783 inner: RequestState,
3784 path: PathOwned,
3788 stats: stats::Scope,
3791}
3792
3793enum RequestState {
3794 Failed(Error),
3797 Pending(kio::Consumer<PendingBroadcast>),
3799}
3800
3801impl Requesting {
3802 fn failed(error: Error) -> Self {
3803 Self::new(RequestState::Failed(error))
3804 }
3805
3806 fn queued(consumer: kio::Consumer<PendingBroadcast>) -> Self {
3807 Self::new(RequestState::Pending(consumer))
3808 }
3809
3810 pub fn is_queued(&self) -> bool {
3819 matches!(self.inner, RequestState::Pending(_))
3820 }
3821
3822 fn new(inner: RequestState) -> Self {
3823 Self {
3824 inner,
3825 path: PathOwned::default(),
3826 stats: stats::Scope::default(),
3827 }
3828 }
3829
3830 fn with_path(mut self, path: PathOwned) -> Self {
3831 self.path = path;
3832 self
3833 }
3834
3835 fn with_stats(mut self, scope: stats::Scope) -> Self {
3837 self.stats = scope;
3838 self
3839 }
3840
3841 fn hand_out(&self, broadcast: broadcast::Consumer) -> broadcast::Consumer {
3843 broadcast.with_path(self.path.clone()).with_stats(self.stats.clone())
3844 }
3845
3846 pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<broadcast::Consumer, Error>> {
3848 match &self.inner {
3849 RequestState::Failed(error) => Poll::Ready(Err(error.clone())),
3850 RequestState::Pending(consumer) => Poll::Ready(
3851 match ready!(consumer.poll(waiter, |state| match &state.resolved {
3852 Some(result) => Poll::Ready(result.clone()),
3853 None => Poll::Pending,
3854 })) {
3855 Ok(result) => result.map(|broadcast| self.hand_out(broadcast)),
3856 Err(_closed) => Err(Error::Unroutable),
3858 },
3859 ),
3860 }
3861 }
3862}
3863
3864impl kio::Pollable for Requesting {
3865 type Output = Result<broadcast::Consumer, Error>;
3866
3867 fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
3868 self.poll_ok(waiter)
3869 }
3870}
3871
3872pub trait Consume<T> {
3880 fn consume(&self) -> T;
3882}
3883
3884impl<T, U: Consume<T>> Consume<T> for &U {
3885 fn consume(&self) -> T {
3886 (**self).consume()
3887 }
3888}
3889
3890impl Consume<Consumer> for Producer {
3891 fn consume(&self) -> Consumer {
3892 Consumer::from_producer(self, stats::Session::default())
3896 }
3897}
3898
3899impl Consume<Consumer> for Consumer {
3900 fn consume(&self) -> Consumer {
3901 self.clone()
3902 }
3903}
3904
3905impl Consume<broadcast::Consumer> for broadcast::Producer {
3906 fn consume(&self) -> broadcast::Consumer {
3907 self.consume()
3909 }
3910}
3911
3912impl Consume<broadcast::Consumer> for broadcast::Consumer {
3913 fn consume(&self) -> broadcast::Consumer {
3914 self.clone()
3915 }
3916}
3917
3918impl Consume<track::Consumer> for track::Producer {
3919 fn consume(&self) -> track::Consumer {
3920 self.consume()
3921 }
3922}
3923
3924impl Consume<track::Consumer> for track::Consumer {
3925 fn consume(&self) -> track::Consumer {
3926 self.clone()
3927 }
3928}
3929
3930#[derive(Clone)]
3936pub struct Consumer {
3937 hop: Hop,
3939 scope: OriginScope,
3940
3941 root: PathOwned,
3943
3944 shared: kio::Shared<OriginState>,
3947
3948 stats: stats::Session,
3952
3953 horizon: Horizon,
3958
3959 hidden: Hidden,
3961
3962 pool: cache::Pool,
3965 cache_duration: Duration,
3966
3967 tasks: TasksWeak,
3971
3972 timers: Clock,
3975}
3976
3977impl Consumer {
3978 fn from_producer(producer: &Producer, stats: stats::Session) -> Self {
3979 Self {
3980 hop: producer.hop,
3981 scope: producer.scope.clone(),
3982 root: producer.root.clone(),
3983 shared: producer.shared.clone(),
3984 stats,
3985 horizon: Horizon::default(),
3986 hidden: Hidden::default(),
3987 pool: producer.pool.clone(),
3988 cache_duration: producer.cache_duration,
3989 tasks: producer.tasks.downgrade(),
3990 timers: producer.timers.clone(),
3991 }
3992 }
3993
3994 pub fn hop(&self) -> Hop {
3996 self.hop
3997 }
3998
3999 pub(crate) fn excluding(mut self, peer: Hop) -> Self {
4007 self.horizon.exclude = Some(peer);
4008 self
4009 }
4010
4011 pub fn local(mut self) -> Self {
4018 self.horizon.local = true;
4019 self
4020 }
4021
4022 pub fn with_hidden(mut self, hidden: bool) -> Self {
4027 self.hidden.include = hidden;
4028 self
4029 }
4030
4031 pub(crate) fn beyond(mut self, outer: &Consumer) -> Self {
4034 self.hidden.beyond = Some(
4035 outer
4036 .hidden
4037 .from
4038 .clone()
4039 .map(|head| vec![head])
4040 .unwrap_or_else(|| interest_prefixes(&outer.scope.allowed)),
4041 );
4042 self
4043 }
4044
4045 pub(crate) fn discovery(mut self, hidden: bool) -> Self {
4047 self.hidden.include = hidden;
4048 self.hidden.from = Some(self.root.clone());
4049 self
4050 }
4051
4052 pub(crate) fn includes_hidden(&self) -> bool {
4054 self.hidden.include
4055 }
4056
4057 pub fn with_stats(mut self, session: stats::Session) -> Self {
4061 self.stats = session;
4062 self
4063 }
4064
4065 fn untagged(&self) -> Self {
4069 Self {
4070 stats: stats::Session::default(),
4071 ..self.clone()
4072 }
4073 }
4074
4075 pub(crate) fn empty(&self) -> Self {
4080 Self {
4081 scope: OriginScope::empty(),
4082 ..self.clone()
4083 }
4084 }
4085
4086 pub fn announced(&self) -> AnnounceConsumer {
4095 let state = kio::Producer::<OriginConsumerState>::default();
4096 let cursor = |root: PathOwned,
4097 allowed: Patterns,
4098 hidden: Hidden,
4099 mount: Option<&Mount>,
4100 under: PathOwned,
4101 holes: Vec<PathOwned>| TableCursor {
4102 root,
4103 heads: interest_prefixes(&allowed),
4104 allowed,
4105 horizon: self.horizon,
4106 hidden,
4107 state: state.clone(),
4108 under,
4109 holes,
4110 named: mount.map(|mount| (mount.clone(), self.scope.allowed.clone())),
4111 current: HashMap::new(),
4112 };
4113
4114 if let Some(mount) = self.scope.mount(&self.root) {
4117 let Some(root) = mount.resolve(&self.root) else {
4119 return AnnounceConsumer::new(
4120 self.root.clone(),
4121 Vec::new(),
4122 state,
4123 self.stats.clone(),
4124 &self.shared,
4125 self.timers.clone(),
4126 );
4127 };
4128 let cursors = vec![cursor(
4129 root,
4130 mount.translate(&self.scope.allowed),
4131 self.hidden.translate(mount),
4132 Some(mount),
4133 PathOwned::default(),
4134 Vec::new(),
4135 )];
4136 return AnnounceConsumer::new(
4137 self.root.clone(),
4138 cursors,
4139 state,
4140 self.stats.clone(),
4141 &self.shared,
4142 self.timers.clone(),
4143 );
4144 }
4145
4146 let heads = interest_prefixes(&self.scope.allowed);
4149 let mut holes = Vec::new();
4150 let mut cursors = Vec::new();
4151 for mount in self.scope.mounts.iter() {
4152 let Some(under) = mount.at.strip_prefix(&self.root) else {
4153 continue;
4154 };
4155 holes.push(mount.at.clone());
4156 let allowed = mount.translate(&self.scope.allowed);
4157 if allowed.is_empty()
4160 || !(self.hidden.include
4161 || !hides(
4162 self.hidden.from.as_ref().map(std::slice::from_ref).unwrap_or(&heads),
4163 &mount.at,
4164 )) {
4165 continue;
4166 }
4167 cursors.push(cursor(
4168 mount.target.clone(),
4169 allowed,
4170 self.hidden.translate(mount),
4171 Some(mount),
4172 under.to_owned(),
4173 Vec::new(),
4174 ));
4175 }
4176 cursors.insert(
4177 0,
4178 cursor(
4179 self.root.clone(),
4180 self.scope.allowed.clone(),
4181 self.hidden.clone(),
4182 None,
4183 PathOwned::default(),
4184 holes,
4185 ),
4186 );
4187 AnnounceConsumer::new(
4188 self.root.clone(),
4189 cursors,
4190 state,
4191 self.stats.clone(),
4192 &self.shared,
4193 self.timers.clone(),
4194 )
4195 }
4196
4197 pub fn consume(&self) -> Self {
4199 self.clone()
4200 }
4201
4202 #[cfg(test)]
4205 pub(crate) fn get_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
4206 let full = self.root.join(path).to_owned();
4207 if !self.scope.permits(&full) {
4208 return None;
4209 }
4210 let full = self.scope.resolve(&full)?;
4211 let table = self.shared.lock();
4212 table
4213 .routes
4214 .at(&full)
4215 .filter(|entry| entry.local)
4216 .min_by_key(|entry| route_order(&entry.prefix, entry))
4217 .and_then(|entry| entry.source.clone())
4218 }
4219
4220 pub async fn routed(&self, path: impl AsPath) -> Option<Route> {
4230 let path = path.as_path();
4231
4232 let consumer = match Pattern::subtree(path.as_str()) {
4237 Ok(subtree) => self.scope("", &Patterns::from(subtree)).ok()?,
4238 Err(InvalidPattern::TooManySegments) => self.clone(),
4239 Err(_) => return None,
4240 };
4241
4242 if !consumer.allowed().matches(path.as_str()) {
4246 return None;
4247 }
4248
4249 let mut announced = consumer.untagged().with_hidden(true).announced();
4253 loop {
4254 let update = announced.next().await?;
4255 if update.kind.is_active() && path.has_prefix(&update.prefix) {
4256 return Some(update.route);
4257 }
4258 }
4259 }
4260
4261 pub async fn routed_broadcast(&self, path: impl AsPath) -> Result<broadcast::Consumer, Error> {
4275 let path = path.as_path();
4276
4277 if !self.allowed().matches(path.as_str()) {
4280 return Err(Error::Unauthorized);
4281 }
4282 loop {
4283 let (watch, seen) = {
4292 let mut table = self.shared.lock();
4293 if table.closed {
4294 return Err(Error::Closed);
4295 }
4296 let named = self.root.join(&path);
4297 let resolved = self.scope.resolve(&named).ok_or(BoundsExceeded)?;
4298 let watch = table.watch(&self.shared, &resolved);
4299 let seen = watch.seen();
4300 (watch, seen)
4301 };
4302 match self.request_broadcast(&path).await {
4303 Ok(broadcast) => return Ok(broadcast),
4304 Err(Error::Unroutable) => {
4305 kio::wait(|waiter| watch.poll_changed(waiter, seen)).await;
4306 }
4307 Err(Error::Dropped) if self.shared.lock().closed => return Err(Error::Closed),
4310 Err(err) => return Err(err),
4311 }
4312 }
4313 }
4314
4315 pub fn scope(&self, root: impl AsPath, patterns: &Patterns) -> Result<Consumer, Error> {
4322 let root = self.root.join(root).to_owned();
4323 let rooted = patterns.rooted(root.as_str()).map_err(|_| BoundsExceeded)?;
4324 let scope = self.scope.narrow(&rooted).ok_or(Error::Unauthorized)?;
4325 Ok(Consumer {
4326 scope,
4327 root,
4328 ..self.clone()
4329 })
4330 }
4331
4332 pub fn request_broadcast(&self, path: impl AsPath) -> kio::Pending<Requesting> {
4355 let path = path.as_path();
4356
4357 let named = self.root.join(&path).to_owned();
4361 let scope = self.stats.egress(&named);
4362 let requested = path.to_owned();
4366
4367 if !self.scope.permits(&named) {
4369 return kio::Pending::new(Requesting::failed(Error::Unauthorized));
4370 }
4371
4372 let Some(absolute) = self.scope.resolve(&named).map(|path| path.to_owned()) else {
4375 return kio::Pending::new(Requesting::failed(BoundsExceeded.into()));
4376 };
4377
4378 let mut state = self.shared.lock();
4379
4380 if state.closed {
4382 return kio::Pending::new(Requesting::failed(Error::Closed));
4383 }
4384
4385 let horizon = self.horizon.effective(&state.routes, &absolute.as_path());
4386
4387 if state
4391 .best_route(&absolute.as_path(), horizon, Pin::Any, &HashSet::new())
4392 .is_none()
4393 {
4394 return kio::Pending::new(Requesting::failed(Error::Unroutable));
4395 }
4396
4397 let key = (absolute.clone(), horizon);
4405 if let Some(front) = state.fronts.get(&key) {
4406 let pin = *front.pin.lock();
4407 let current = state
4408 .best_route(&absolute.as_path(), horizon, Pin::Any, &HashSet::new())
4409 .is_some_and(|entry| entry.qualifies(pin));
4410 if current {
4411 let pending = Requesting::queued(front.request.consume())
4412 .with_path(requested)
4413 .with_stats(scope);
4414 return kio::Pending::new(pending);
4415 }
4416 state.fronts.remove(&key);
4417 }
4418
4419 let broadcast = broadcast::Producer::new_spliced(broadcast::Info {
4424 pool: self.pool.clone(),
4425 cache_duration: self.cache_duration,
4426 path: absolute.clone(),
4427 });
4428 let request = kio::Producer::<PendingBroadcast>::default();
4429 let consumer = request.consume();
4430 let watch = state.watch(&self.shared, &absolute);
4431 let pin = kio::Lock::new(Pin::Any);
4432 state.fronts.insert(
4433 key,
4434 RemoteFront {
4435 request: request.clone(),
4436 broadcast: broadcast.consume().weak(),
4437 pin: pin.clone(),
4438 },
4439 );
4440 drop(state);
4443 self.tasks.push(run_front(FrontTask {
4444 shared: self.shared.clone(),
4445 broadcast,
4446 path: absolute,
4447 horizon,
4448 watch,
4449 request,
4450 pin,
4451 timers: self.timers.clone(),
4452 }));
4453 kio::Pending::new(Requesting::queued(consumer).with_path(requested).with_stats(scope))
4454 }
4455
4456 pub fn root(&self) -> &Path<'_> {
4458 &self.root
4459 }
4460
4461 pub fn allowed(&self) -> Patterns {
4463 self.scope.relative(&self.root)
4464 }
4465
4466 pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
4468 self.root.join(path)
4469 }
4470}
4471
4472pub struct AnnounceConsumer {
4477 ids: Vec<ConsumerId>,
4480 shared: kio::Shared<OriginState>,
4481 root: PathOwned,
4482
4483 state: kio::Producer<OriginConsumerState>,
4486
4487 stats: stats::Session,
4490
4491 guards: HashMap<PathOwned, stats::Announce>,
4495
4496 park: kio::Park,
4499
4500 update_hold: Duration,
4503 held: HashMap<PathOwned, Held>,
4504 timers: Clock,
4505 hold: crate::runtime::Deadline<Clock>,
4506}
4507
4508impl AnnounceConsumer {
4509 fn new(
4510 root: PathOwned,
4511 cursors: Vec<TableCursor>,
4512 state: kio::Producer<OriginConsumerState>,
4513 stats: stats::Session,
4514 shared: &kio::Shared<OriginState>,
4515 timers: Clock,
4516 ) -> Self {
4517 let mut ids = Vec::with_capacity(cursors.len());
4518 let update_hold;
4519 {
4520 let mut table = shared.lock();
4521 update_hold = table.update_hold;
4522 if table.closed {
4523 if let Ok(mut state) = state.write() {
4525 state.ended = true;
4526 }
4527 } else {
4528 for cursor in cursors {
4529 let id = ConsumerId::new();
4530 table.register_cursor(id, cursor);
4531 ids.push(id);
4532 }
4533 }
4534 }
4535
4536 Self {
4537 ids,
4538 shared: shared.clone(),
4539 root,
4540 state,
4541 stats,
4542 guards: HashMap::new(),
4543 park: kio::Park::default(),
4544 update_hold,
4545 held: HashMap::new(),
4546 hold: crate::runtime::Deadline::new(&timers),
4547 timers,
4548 }
4549 }
4550
4551 fn hand_out(&mut self, update: AnnounceUpdate) -> AnnounceUpdate {
4553 let absolute = self.root.join(&update.prefix).to_owned();
4554 if update.kind.is_active() {
4555 let scope = self.stats.egress(&absolute);
4556 self.guards
4557 .entry(update.prefix.clone())
4558 .or_insert_with(|| scope.announce());
4559 } else {
4560 self.guards.remove(&update.prefix);
4561 }
4562 update
4563 }
4564
4565 pub async fn next(&mut self) -> Option<AnnounceUpdate> {
4573 kio::wait(|waiter| self.poll_next(waiter)).await
4574 }
4575
4576 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Option<AnnounceUpdate>> {
4582 loop {
4583 let now = self.timers.try_now();
4584 let hold = self.update_hold;
4585 let held = &mut self.held;
4586 let mut ready = None;
4587 let mut wake = None;
4588 let update = match self.state.poll(waiter, |state| {
4589 if state.pending.is_empty() {
4590 return match state.ended {
4591 true => Poll::Ready(()),
4592 false => Poll::Pending,
4593 };
4594 }
4595 match state.scan(held, now, hold) {
4596 Ok(prefix) => {
4597 ready = Some(prefix);
4598 Poll::Ready(())
4599 }
4600 Err(at) => {
4601 wake = at;
4602 Poll::Pending
4603 }
4604 }
4605 }) {
4606 Poll::Ready(Ok(mut state)) => match ready {
4607 Some(prefix) => state.take_prefix(prefix),
4608 None => {
4609 state.close();
4612 None
4613 }
4614 },
4615 Poll::Ready(Err(_)) => None,
4617 Poll::Pending => {
4618 self.hold.set(wake);
4619 ready!(self.hold.poll(waiter));
4620 continue;
4621 }
4622 };
4623 let Some(update) = update else {
4624 return Poll::Ready(None);
4625 };
4626 self.held.remove(&update.prefix);
4627 return Poll::Ready(Some(self.hand_out(update)));
4628 }
4629 }
4630
4631 pub fn try_next(&mut self) -> Option<AnnounceUpdate> {
4636 let now = self.timers.try_now();
4637 let mut state = self.state.write().ok()?;
4638 let prefix = match state.scan(&mut self.held, now, self.update_hold) {
4639 Ok(prefix) => prefix,
4640 Err(wake) => {
4641 drop(state);
4644 self.hold.set(wake);
4645 return None;
4646 }
4647 };
4648 self.held.remove(&prefix);
4649 let update = state.take_prefix(prefix)?;
4650 drop(state);
4651 Some(self.hand_out(update))
4652 }
4653
4654 pub fn is_closed(&self) -> bool {
4656 let state = self.state.read();
4657 state.is_closed() || state.ended
4658 }
4659
4660 pub fn root(&self) -> &Path<'_> {
4662 &self.root
4663 }
4664
4665 pub fn absolute(&self, prefix: impl AsPath) -> Path<'_> {
4667 self.root.join(prefix)
4668 }
4669}
4670
4671impl futures::Stream for AnnounceConsumer {
4672 type Item = AnnounceUpdate;
4673
4674 fn poll_next(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Option<Self::Item>> {
4675 let this = self.get_mut();
4676 let waiter = this.park.hold(cx).clone();
4677 this.poll_next(&waiter)
4678 }
4679}
4680
4681impl Drop for AnnounceConsumer {
4682 fn drop(&mut self) {
4683 let mut shared = self.shared.lock();
4684 for id in &self.ids {
4685 if let Some(cursor) = shared.cursors.remove(id) {
4686 for head in &cursor.heads {
4687 shared.routes.remove_cursor(head, *id);
4688 }
4689 }
4690 }
4691 }
4692}
4693
4694#[cfg(test)]
4695use futures::FutureExt;
4696
4697#[cfg(test)]
4698#[allow(missing_docs)] impl AnnounceConsumer {
4700 pub fn assert_next_active(&mut self, expected: impl AsPath) -> Route {
4702 let expected = expected.as_path();
4703 let update = self.next().now_or_never().expect("next blocked").expect("no next");
4704 assert_eq!(update.prefix, expected, "wrong prefix");
4705 assert!(update.kind.is_active(), "should be an active route");
4706 update.route
4707 }
4708
4709 pub fn assert_try_next_active(&mut self, expected: impl AsPath) -> Route {
4711 let expected = expected.as_path();
4712 let update = self.try_next().expect("no next");
4713 assert_eq!(update.prefix, expected, "wrong prefix");
4714 assert!(update.kind.is_active(), "should be an active route");
4715 update.route
4716 }
4717
4718 pub fn assert_next_ended(&mut self, expected: impl AsPath) {
4720 let expected = expected.as_path();
4721 let update = self.next().now_or_never().expect("next blocked").expect("no next");
4722 assert_eq!(update.prefix, expected, "wrong prefix");
4723 assert_eq!(update.kind, AnnounceKind::Retracted, "should be a retraction");
4724 }
4725
4726 pub fn assert_next_wait(&mut self) {
4727 if let Some(res) = self.next().now_or_never() {
4728 panic!("next should block: got {:?}", res.map(|u| u.prefix));
4729 }
4730 }
4731}
4732
4733#[cfg(test)]
4737pub(crate) trait ProduceTest {
4738 fn produce(self) -> Producer;
4739}
4740
4741#[cfg(test)]
4742impl ProduceTest for Config {
4743 fn produce(self) -> Producer {
4744 let (producer, driver) = Producer::new(self);
4745 if tokio::runtime::Handle::try_current().is_ok() {
4746 tokio::spawn(crate::time::run(driver));
4747 } else {
4748 std::mem::forget(driver);
4751 }
4752 producer
4753 }
4754}
4755
4756#[cfg(test)]
4757impl ProduceTest for Hop {
4758 fn produce(self) -> Producer {
4759 Config::new(self).produce()
4760 }
4761}
4762
4763#[cfg(test)]
4764mod tests {
4765 use super::*;
4766 use futures::FutureExt;
4767
4768 fn origin(id: u64) -> Hop {
4769 Hop::new(id).unwrap()
4770 }
4771
4772 fn hops(ids: &[u64]) -> Hops {
4773 let mut list = Hops::new();
4774 for &id in ids {
4775 list.push(if id == 0 { Hop::UNKNOWN } else { origin(id) }).unwrap();
4776 }
4777 list
4778 }
4779
4780 fn scopes(prefixes: &[&str]) -> Patterns {
4782 prefixes
4783 .iter()
4784 .map(|prefix| Pattern::subtree(prefix).unwrap())
4785 .collect()
4786 }
4787
4788 #[test]
4789 fn default_config_mints_a_real_hop() {
4790 let config = Config::default();
4791 assert_ne!(config.hop, Hop::UNKNOWN);
4792 let (producer, _driver) = Producer::new(config.clone());
4793 assert_eq!(producer.hop(), config.hop);
4794 assert_eq!(producer.consume().hop(), config.hop);
4795 }
4796
4797 #[test]
4798 fn random_hops_fit_legacy_lite_clients() {
4799 for _ in 0..32 {
4800 assert!(Hop::random().id() < 1u64 << 53);
4801 }
4802 }
4803
4804 async fn settle(mut check: impl FnMut() -> bool) {
4807 for _ in 0..100 {
4808 if check() {
4809 return;
4810 }
4811 tokio::task::yield_now().await;
4812 }
4813 panic!("condition never settled");
4814 }
4815
4816 async fn queued(server: &Dynamic) -> Request {
4818 let mut request = None;
4819 settle(|| match server.poll_requested_broadcast(&kio::Waiter::noop()) {
4820 Poll::Ready(Ok(popped)) => {
4821 request = Some(popped);
4822 true
4823 }
4824 _ => false,
4825 })
4826 .await;
4827 request.unwrap()
4828 }
4829
4830 async fn next_group(subscription: &mut crate::track::Subscriber) -> Result<Option<crate::group::Consumer>, Error> {
4832 let mut next = None;
4833 settle(|| match subscription.poll_recv_group(&kio::Waiter::noop()) {
4834 Poll::Ready(result) => {
4835 next = Some(result);
4836 true
4837 }
4838 Poll::Pending => false,
4839 })
4840 .await;
4841 next.unwrap()
4842 }
4843
4844 #[tokio::test]
4845 async fn announce_and_retract() {
4846 let producer = origin(1).produce();
4847 let consumer = producer.consume();
4848 let mut announced = consumer.announced();
4849 announced.assert_next_wait();
4850
4851 let announcement = producer.announce("room/alice", Route::default()).unwrap();
4852 let route = announced.assert_next_active("room/alice");
4853 assert!(route.hops.is_empty());
4854 assert_eq!(route.cost, Cost::default());
4855 announced.assert_next_wait();
4856
4857 drop(announcement);
4858 announced.assert_next_ended("room/alice");
4859 announced.assert_next_wait();
4860 }
4861
4862 #[tokio::test]
4865 async fn hidden_routes_need_an_opt_in() {
4866 let producer = origin(1).produce();
4867 let consumer = producer.consume();
4868 let _visible = producer.announce("room/alice", Route::default()).unwrap();
4869 let _stats = producer.announce(".stats/node", Route::default()).unwrap();
4870 let _nested = producer.announce("room/.internal", Route::default()).unwrap();
4871 let _suffix = producer.announce("room/catalog.pro", Route::default()).unwrap();
4873
4874 let mut announced = consumer.announced();
4875 announced.assert_next_active("room/alice");
4876 announced.assert_next_active("room/catalog.pro");
4877 announced.assert_next_wait();
4878
4879 let mut announced = consumer.clone().with_hidden(true).announced();
4880 announced.assert_next_active(".stats/node");
4881 announced.assert_next_active("room/.internal");
4882 announced.assert_next_active("room/alice");
4883 announced.assert_next_active("room/catalog.pro");
4884 announced.assert_next_wait();
4885
4886 let mut announced = consumer
4888 .scope(".stats", &Patterns::from(Pattern::all()))
4889 .unwrap()
4890 .announced();
4891 announced.assert_next_active("node");
4892 announced.assert_next_wait();
4893 let mut announced = consumer.scope("", &scopes(&["room/.internal"])).unwrap().announced();
4894 announced.assert_next_active("room/.internal");
4895 announced.assert_next_wait();
4896
4897 let mut announced = consumer.clone().with_hidden(true).beyond(&consumer).announced();
4900 announced.assert_next_active(".stats/node");
4901 announced.assert_next_active("room/.internal");
4902 announced.assert_next_wait();
4903 let room = consumer.scope("", &scopes(&["room"])).unwrap().beyond(&consumer);
4904 let mut announced = room.announced();
4905 announced.assert_next_wait();
4906 let stats = consumer.scope("", &scopes(&[".stats"])).unwrap().beyond(&consumer);
4907 let mut announced = stats.announced();
4908 announced.assert_next_active(".stats/node");
4909 announced.assert_next_wait();
4910 }
4911
4912 #[tokio::test]
4914 async fn hidden_broadcast_resolves_by_path() {
4915 let producer = origin(1).produce();
4916 let consumer = producer.consume();
4917 let broadcast = producer.create_broadcast(".stats/node").unwrap();
4918 broadcast.announce(Route::default()).unwrap();
4919
4920 consumer.announced().assert_next_wait();
4921 let resolved = consumer.request_broadcast(".stats/node").await.expect("resolves");
4922 assert_eq!(resolved.info().path.as_str(), ".stats/node");
4923 }
4924
4925 fn mounted(producer: &Producer, pid: &str, patterns: &[&str]) -> Consumer {
4927 let patterns: Patterns = patterns.iter().map(|pattern| pattern.parse().unwrap()).collect();
4928 producer
4929 .mount(format!("{pid}/.svc"), format!(".svc/{pid}"))
4930 .unwrap()
4931 .scope(pid, &patterns)
4932 .unwrap()
4933 .consume()
4934 }
4935
4936 #[tokio::test]
4939 async fn mount_resolves_on_the_target_front() {
4940 let producer = origin(1).produce();
4941 let server = producer.dynamic(".svc", Route::default()).unwrap();
4942 let project = mounted(&producer, "p1", &["**"]);
4943
4944 let through = project.request_broadcast(".svc/foo");
4945 let direct = producer.consume().request_broadcast(".svc/p1/foo");
4946
4947 let request = queued(&server).await;
4948 assert_eq!(request.path().as_str(), ".svc/p1/foo");
4949 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
4950 let source = broadcast::Info::new().produce();
4951 request.accept(&source);
4952
4953 let through = through.await.expect("resolves");
4954 let direct = direct.await.expect("resolves");
4955 assert!(through.is_clone(&direct));
4956 assert_eq!(through.info().path.as_str(), ".svc/foo");
4958 }
4959
4960 #[tokio::test]
4963 async fn mount_presents_target_routes_under_the_mount() {
4964 let producer = origin(1).produce();
4965 let _claim = producer.announce(".svc", Route::default()).unwrap();
4966 let foo = producer.publish(".svc/p1/foo", Route::default()).unwrap();
4967 let _other = producer.publish(".svc/p2/bar", Route::default()).unwrap();
4968 let _cam = producer.publish("p1/cam", Route::default()).unwrap();
4969 let _shadowed = producer.publish("p1/.svc/forged", Route::default()).unwrap();
4970 let project = mounted(&producer, "p1", &["**"]);
4971
4972 let mut announced = project.announced();
4974 announced.assert_next_active("cam");
4975 announced.assert_next_wait();
4976
4977 let mut announced = project.clone().with_hidden(true).announced();
4978 announced.assert_next_active(".svc");
4979 announced.assert_next_active(".svc/foo");
4980 announced.assert_next_active("cam");
4981 announced.assert_next_wait();
4982
4983 let mut inside = project
4985 .scope(".svc", &Patterns::from(Pattern::all()))
4986 .unwrap()
4987 .announced();
4988 inside.assert_next_active("");
4989 inside.assert_next_active("foo");
4990 inside.assert_next_wait();
4991
4992 drop(foo);
4993 announced.assert_next_ended(".svc/foo");
4994 inside.assert_next_ended("foo");
4995 announced.assert_next_wait();
4996
4997 let err = project.request_broadcast(".svc/forged").await.err().unwrap();
4999 assert!(matches!(err, Error::NotFound | Error::Unroutable), "{err:?}");
5000 }
5001
5002 #[tokio::test]
5005 async fn mount_authorizes_the_named_path() {
5006 let producer = origin(1).produce();
5007 let _foo = producer.publish(".svc/p1/foo", Route::default()).unwrap();
5008 let _bar = producer.publish(".svc/p1/bar", Route::default()).unwrap();
5009
5010 let granted = mounted(&producer, "p1", &["foo", ".svc/foo"]);
5011 granted.request_broadcast(".svc/foo").await.expect("granted");
5012 let refused = granted
5013 .request_broadcast(".svc/bar")
5014 .now_or_never()
5015 .expect("refused at once");
5016 assert!(matches!(refused, Err(Error::Unauthorized)));
5017 let mut announced = granted.clone().with_hidden(true).announced();
5018 announced.assert_next_active(".svc/foo");
5019 announced.assert_next_wait();
5020
5021 let narrower = mounted(&producer, "p1", &["foo"]);
5022 let refused = narrower
5023 .request_broadcast(".svc/foo")
5024 .now_or_never()
5025 .expect("refused at once");
5026 assert!(matches!(refused, Err(Error::Unauthorized)));
5027 narrower.clone().with_hidden(true).announced().assert_next_wait();
5028
5029 let other = mounted(&producer, "p2", &["**"]);
5031 other.clone().with_hidden(true).announced().assert_next_wait();
5032 let err = other.request_broadcast(".svc/foo").await.err().unwrap();
5033 assert!(matches!(err, Error::Unroutable));
5034 }
5035
5036 #[tokio::test]
5039 async fn mount_captures_the_named_path() {
5040 let producer = origin(1).produce();
5041 let _foo = producer.publish(".svc/p1/foo", Route::default()).unwrap();
5042 let project = mounted(&producer, "p1", &["**"]).with_hidden(true);
5043
5044 let update = project.announced().try_next().expect("foo");
5045 assert_eq!(update.prefix.as_str(), ".svc/foo");
5046 assert_eq!(update.captures, Some(vec![".svc/foo".parse::<Pattern>().unwrap()]));
5047
5048 let inside = project.scope(".svc", &Patterns::from(Pattern::all())).unwrap();
5049 let update = inside.announced().try_next().expect("foo");
5050 assert_eq!(update.prefix.as_str(), "foo");
5051 assert_eq!(update.captures, Some(vec!["foo".parse::<Pattern>().unwrap()]));
5052 }
5053
5054 #[tokio::test]
5057 async fn mount_keeps_a_max_depth_target() {
5058 let producer = origin(1).produce();
5059 let deep = vec!["d"; Path::MAX_PARTS].join("/");
5060 let _leaf = producer.publish(deep.as_str(), Route::default()).unwrap();
5061 let project = producer
5062 .mount("p1/.svc", deep.as_str())
5063 .unwrap()
5064 .scope("p1", &Patterns::from(Pattern::all()))
5065 .unwrap()
5066 .consume()
5067 .with_hidden(true);
5068
5069 let mut announced = project.announced();
5070 announced.assert_next_active(".svc");
5071 announced.assert_next_wait();
5072
5073 let err = project.request_broadcast(".svc/x").await.err().unwrap();
5075 assert!(matches!(err, Error::BoundsExceeded(_)), "{err:?}");
5076 let inside = project.scope(".svc/x", &Patterns::from(Pattern::all())).unwrap();
5077 inside.announced().assert_next_wait();
5078 }
5079
5080 #[tokio::test]
5083 async fn mount_bounds_the_named_path() {
5084 let producer = origin(1).produce();
5085 let _near = producer.publish("t/x", Route::default()).unwrap();
5086 let deep = format!("t/{}", vec!["d"; Path::MAX_PARTS - 1].join("/"));
5087 let _deep = producer.publish(deep.as_str(), Route::default()).unwrap();
5088 let project = producer
5089 .mount("p1/a/b", "t")
5090 .unwrap()
5091 .scope("p1", &Patterns::from(Pattern::all()))
5092 .unwrap()
5093 .consume();
5094
5095 let mut announced = project.announced();
5096 announced.assert_next_active("a/b/x");
5097 announced.assert_next_wait();
5098
5099 let over = vec!["d"; Path::MAX_PARTS + 1].join("/");
5100 assert!(matches!(
5101 producer.mount(over.as_str(), "t"),
5102 Err(Error::BoundsExceeded(_))
5103 ));
5104 }
5105
5106 #[tokio::test]
5108 async fn mount_is_read_only() {
5109 let producer = origin(1).produce();
5110 let project = producer
5111 .mount("p1/.svc", ".svc/p1")
5112 .unwrap()
5113 .scope("p1", &Patterns::from(Pattern::all()))
5114 .unwrap();
5115 assert!(matches!(project.create_broadcast(".svc/foo"), Err(Error::Unauthorized)));
5116 assert!(matches!(
5117 project.dynamic(".svc", Route::default()),
5118 Err(Error::Unauthorized)
5119 ));
5120 assert!(matches!(
5121 project.dynamic(".svc/foo", Route::default()),
5122 Err(Error::Unauthorized)
5123 ));
5124 project.publish("cam", Route::default()).unwrap();
5125 }
5126
5127 #[test]
5129 fn mount_never_widens_a_scope() {
5130 let (producer, _driver) = Producer::new(Config::new(origin(1)));
5131 let project = producer.scope("", &scopes(&["p1"])).unwrap();
5132 assert!(matches!(project.mount("p1/.svc", ".svc/p1"), Err(Error::Unauthorized)));
5133
5134 let mounted = producer.mount("p1/.svc", ".svc/p1").unwrap();
5135 assert!(matches!(mounted.mount("p1/.svc/x", ".other"), Err(Error::Duplicate)));
5136 assert!(matches!(mounted.mount("p1", ".other"), Err(Error::Duplicate)));
5137 assert!(matches!(mounted.mount("p2", "p1/.svc/x"), Err(Error::Duplicate)));
5139 assert!(matches!(mounted.mount("p2", "p1"), Err(Error::Duplicate)));
5140 assert!(matches!(mounted.mount(".svc/p1/x", ".other"), Err(Error::Duplicate)));
5142 assert!(matches!(mounted.mount(".svc", ".other"), Err(Error::Duplicate)));
5143 mounted.mount("p1/.other", ".other/p1").unwrap();
5144 mounted.mount("p2/.svc", ".svc/p1").unwrap();
5146 for (at, target) in [("a", "a/b"), ("a/b", "a"), ("a", "a")] {
5148 assert!(
5149 matches!(producer.mount(at, target), Err(Error::Duplicate)),
5150 "{at} -> {target}"
5151 );
5152 }
5153 }
5154
5155 #[test]
5157 fn mount_refuses_a_wildcard_mount_point() {
5158 let (producer, _driver) = Producer::new(Config::new(origin(1)));
5159 for at in ["*", "p1/*", "p1/**", "p1/a*"] {
5160 assert!(
5161 matches!(producer.mount(at, ".svc/p1"), Err(Error::InvalidPath(_))),
5162 "{at}"
5163 );
5164 }
5165 }
5166
5167 #[tokio::test]
5170 async fn mount_egress_counts_under_the_named_path() {
5171 let registry = stats::Registry::new(stats::Config::new());
5172 let producer = origin(1).produce();
5173 let _foo = producer.publish(".svc/p1/foo", Route::default()).unwrap();
5174 let project = mounted(&producer, "p1", &["**"])
5175 .with_stats(registry.tier(stats::Tier::default()).session("p1"))
5176 .with_hidden(true);
5177
5178 let mut announced = project.announced();
5179 announced.assert_next_active(".svc/foo");
5180 project.request_broadcast(".svc/foo").await.expect("resolves");
5181
5182 let mut report = stats::Report::default();
5183 registry.report(&mut report);
5184 let paths: Vec<_> = report
5185 .traffic
5186 .iter()
5187 .map(|entry| entry.path.as_str().to_string())
5188 .collect();
5189 assert_eq!(paths, ["p1/.svc/foo"]);
5190 }
5191
5192 #[tokio::test]
5194 async fn hidden_route_announced_later_stays_hidden() {
5195 let producer = origin(1).produce();
5196 let consumer = producer.consume();
5197 let mut announced = consumer.announced();
5198 let mut opted = consumer.clone().with_hidden(true).announced();
5199
5200 let hidden = producer.announce(".stats/node", Route::default()).unwrap();
5201 announced.assert_next_wait();
5202 opted.assert_next_active(".stats/node");
5203
5204 drop(hidden);
5205 announced.assert_next_wait();
5206 opted.assert_next_ended(".stats/node");
5207 }
5208
5209 #[tokio::test]
5210 async fn broadcast_announces_its_own_path() {
5211 let producer = origin(1).produce();
5212 let consumer = producer.consume();
5213 let mut announced = consumer.announced();
5214 let mut peer = consumer.clone().excluding(Hop::UNKNOWN).announced();
5215
5216 let broadcast = producer.create_broadcast("room/alice").unwrap();
5218 announced.assert_next_wait();
5219 peer.assert_next_wait();
5220
5221 broadcast.announce(Route::default().with_cost(3)).unwrap();
5222 assert_eq!(announced.assert_next_active("room/alice").cost, Cost::new(3));
5223 assert_eq!(peer.assert_next_active("room/alice").cost, Cost::new(3));
5224
5225 broadcast.announce(Route::default().with_cost(1)).unwrap();
5227 assert_eq!(announced.assert_next_active("room/alice").cost, Cost::new(1));
5228 assert_eq!(peer.assert_next_active("room/alice").cost, Cost::new(1));
5229
5230 broadcast.unannounce();
5232 announced.assert_next_ended("room/alice");
5233 peer.assert_next_ended("room/alice");
5234 broadcast.unannounce();
5235 announced.assert_next_wait();
5236 let err = consumer.request_broadcast("room/alice").await.err().unwrap();
5237 assert!(matches!(err, Error::Unroutable));
5238
5239 broadcast.announce(Route::default()).unwrap();
5241 announced.assert_next_active("room/alice");
5242 peer.assert_next_active("room/alice");
5243 broadcast.close();
5244 announced.assert_next_ended("room/alice");
5245 peer.assert_next_ended("room/alice");
5246 assert!(matches!(broadcast.announce(Route::default()), Err(Error::Closed)));
5247 announced.assert_next_wait();
5248 }
5249
5250 #[tokio::test]
5251 async fn broadcast_announcement_retracts_with_the_last_producer() {
5252 let producer = origin(1).produce();
5253 let consumer = producer.consume();
5254 let mut announced = consumer.announced();
5255
5256 let broadcast = producer.create_broadcast("room/alice").unwrap();
5257 let clone = broadcast.clone();
5258 broadcast.announce(Route::default()).unwrap();
5259 announced.assert_next_active("room/alice");
5260
5261 drop(broadcast);
5263 announced.assert_next_wait();
5264 drop(clone);
5265 announced.assert_next_ended("room/alice");
5266 }
5267
5268 #[tokio::test]
5269 async fn publish_creates_and_announces_together() {
5270 let producer = origin(1).produce();
5271 let mut announced = producer.consume().announced();
5272 let _broadcast = producer.publish("room/alice", Route::default()).unwrap();
5273 announced.assert_next_active("room/alice");
5274 }
5275
5276 #[tokio::test]
5277 async fn standalone_broadcast_cannot_announce() {
5278 let broadcast = broadcast::Info::new().produce();
5279 assert!(matches!(broadcast.announce(Route::default()), Err(Error::Closed)));
5280 broadcast.unannounce();
5282 }
5283
5284 #[tokio::test]
5285 async fn announce_replays_to_late_cursor() {
5286 let producer = origin(1).produce();
5287 let _a = producer.announce("room/alice", Route::default()).unwrap();
5288 let _b = producer.announce("room/bob", Route::default()).unwrap();
5289
5290 let mut announced = producer.consume().announced();
5291 announced.assert_next_active("room/alice");
5293 announced.assert_next_active("room/bob");
5294 announced.assert_next_wait();
5295 }
5296
5297 #[tokio::test]
5298 async fn announce_keeps_its_prefix_under_a_producer_scope() {
5299 let producer = origin(1).produce();
5300 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
5301
5302 let _a = scoped.announce("", Route::default()).unwrap();
5304 let mut announced = producer.consume().announced();
5305 announced.assert_next_active("");
5306
5307 assert!(matches!(
5309 scoped.announce("other", Route::default()),
5310 Err(Error::Unauthorized)
5311 ));
5312 }
5313
5314 #[tokio::test]
5315 async fn cursor_keeps_an_overlapping_prefix_above_its_scope() {
5316 let producer = origin(1).produce();
5317 let _a = producer.announce("", Route::default()).unwrap();
5318
5319 let consumer = producer.consume().scope("", &scopes(&["room"])).unwrap();
5320 let mut announced = consumer.announced();
5321 announced.assert_next_active("");
5322 }
5323
5324 #[tokio::test]
5325 async fn cursor_root_strips_prefix() {
5326 let producer = origin(1).produce();
5327 let _a = producer.announce("room/alice", Route::default()).unwrap();
5328
5329 let consumer = producer
5330 .consume()
5331 .scope("room", &Patterns::from(Pattern::all()))
5332 .unwrap();
5333 let mut announced = consumer.announced();
5334 announced.assert_next_active("alice");
5335 }
5336
5337 #[tokio::test]
5338 async fn best_route_wins_and_fails_over() {
5339 let producer = origin(1).produce();
5340 let mut announced = producer.consume().announced();
5341
5342 let expensive = producer
5343 .announce("room", Route::default().with_hops(hops(&[10])).with_cost(5))
5344 .unwrap();
5345 let route = announced.assert_next_active("room");
5346 assert_eq!(route.cost, Cost::new(5));
5347
5348 let cheap = producer
5350 .announce("room", Route::default().with_hops(hops(&[20])).with_cost(1))
5351 .unwrap();
5352 let route = announced.assert_next_active("room");
5353 assert_eq!(route.cost, Cost::new(1));
5354
5355 drop(cheap);
5357 let route = announced.assert_next_active("room");
5358 assert_eq!(route.cost, Cost::new(5));
5359
5360 drop(expensive);
5362 announced.assert_next_ended("room");
5363 }
5364
5365 #[tokio::test]
5366 async fn identical_reannounce_is_invisible() {
5367 let producer = origin(1).produce();
5368 let mut announced = producer.consume().announced();
5369
5370 let old = producer
5371 .announce("room", Route::default().with_hops(hops(&[10])))
5372 .unwrap();
5373 let first = announced.assert_next_active("room");
5374 assert_eq!(first.hops.as_slice(), hops(&[10]).as_slice());
5375
5376 let _new = producer
5380 .announce("room", Route::default().with_hops(hops(&[10])))
5381 .unwrap();
5382 announced.assert_next_wait();
5383
5384 drop(old);
5386 announced.assert_next_wait();
5387 }
5388
5389 #[tokio::test]
5390 async fn exclude_hides_routes_through_the_peer() {
5391 let producer = origin(1).produce();
5392 let _a = producer
5393 .announce("room", Route::default().with_hops(hops(&[7])))
5394 .unwrap();
5395
5396 let mut hidden = producer.consume().excluding(origin(7)).announced();
5397 hidden.assert_next_wait();
5398
5399 let mut visible = producer.consume().excluding(origin(8)).announced();
5400 visible.assert_next_active("room");
5401 }
5402
5403 #[tokio::test]
5404 async fn exclude_matches_via_when_the_chain_is_anonymous() {
5405 let producer = origin(1).produce();
5406 let assigned = origin(777);
5407 let _echoed = producer
5408 .announce("echoed", Route::default().with_hops(hops(&[0])).with_via(assigned))
5409 .unwrap();
5410 let _local = producer
5411 .announce("local", Route::default().with_hops(hops(&[10])))
5412 .unwrap();
5413
5414 let mut hidden = producer.consume().excluding(assigned).announced();
5415 hidden.assert_next_active("local");
5416 hidden.assert_next_wait();
5417 }
5418
5419 #[tokio::test]
5420 async fn anonymous_route_loses_to_identified_at_any_cost() {
5421 let producer = origin(1).produce();
5422 let mut announced = producer.consume().announced();
5423
5424 let _anonymous = producer
5425 .announce("room", Route::default().with_hops(hops(&[0])).with_cost(1))
5426 .unwrap();
5427 let route = announced.assert_next_active("room");
5428 assert!(route.is_anonymous());
5429 assert_eq!(route.cost, Cost::new(1));
5430
5431 let _identified = producer
5432 .announce("room", Route::default().with_hops(hops(&[10])).with_cost(5))
5433 .unwrap();
5434 let route = announced.assert_next_active("room");
5435 assert!(!route.is_anonymous());
5436 assert_eq!(route.cost, Cost::new(5));
5437 }
5438
5439 #[tokio::test]
5442 async fn a_stamped_route_ranks_below_an_identified_one() {
5443 let producer = origin(1).produce();
5444 let mut announced = producer.consume().announced();
5445
5446 let mut stamped = Hops::new();
5447 stamped.stamp(origin(5)).unwrap();
5448 assert_eq!(stamped.as_slice(), &[origin(5), Hop::UNKNOWN]);
5449
5450 let _legacy = producer
5451 .announce("room", Route::default().with_hops(stamped).with_cost(0))
5452 .unwrap();
5453 let route = announced.assert_next_active("room");
5454 assert!(route.is_anonymous());
5455
5456 let _identified = producer
5457 .announce("room", Route::default().with_hops(hops(&[10, 11])).with_cost(5))
5458 .unwrap();
5459 let route = announced.assert_next_active("room");
5460 assert_eq!(route.hops.as_slice(), hops(&[10, 11]).as_slice());
5461 }
5462
5463 #[test]
5464 fn stamping_keeps_the_leading_zero_and_names_nothing_else() {
5465 let mut chain = hops(&[0, 7]);
5466 chain.stamp(origin(5)).unwrap();
5467 assert_eq!(chain.as_slice(), hops(&[5, 0, 7]).as_slice());
5468
5469 let mut named = hops(&[7, 0]);
5471 named.stamp(origin(5)).unwrap();
5472 assert_eq!(named.as_slice(), hops(&[7, 0]).as_slice());
5473
5474 let mut full = Hops::try_from(vec![Hop::UNKNOWN; MAX_HOPS]).unwrap();
5476 assert_eq!(full.stamp(origin(5)), Err(InvalidHop::TooMany));
5477 }
5478
5479 #[tokio::test]
5480 async fn anonymous_routes_order_by_cost() {
5481 let producer = origin(1).produce();
5482 let mut announced = producer.consume().announced();
5483
5484 let expensive = producer
5485 .announce("room", Route::default().with_hops(hops(&[0])).with_cost(5))
5486 .unwrap();
5487 let route = announced.assert_next_active("room");
5488 assert_eq!(route.cost, Cost::new(5));
5489
5490 let _cheap = producer
5491 .announce("room", Route::default().with_hops(hops(&[0, 7])).with_cost(1))
5492 .unwrap();
5493 let route = announced.assert_next_active("room");
5494 assert!(route.is_anonymous());
5495 assert_eq!(route.cost, Cost::new(1));
5496
5497 drop(expensive);
5498 announced.assert_next_wait();
5499 }
5500
5501 #[tokio::test]
5502 async fn anonymous_chain_from_identified_peer_still_ranks_last() {
5503 let producer = origin(1).produce();
5504 let mut announced = producer.consume().announced();
5505
5506 let _anonymous = producer
5507 .announce(
5508 "room",
5509 Route::default()
5510 .with_hops(hops(&[0, 7]))
5511 .with_cost(1)
5512 .with_via(origin(7)),
5513 )
5514 .unwrap();
5515 announced.assert_next_active("room");
5516
5517 let _identified = producer
5518 .announce("room", Route::default().with_hops(hops(&[10, 20])).with_cost(5))
5519 .unwrap();
5520 let route = announced.assert_next_active("room");
5521 assert!(!route.is_anonymous());
5522 assert_eq!(route.cost, Cost::new(5));
5523 }
5524
5525 #[tokio::test]
5526 async fn request_prefers_identified_over_cheaper_anonymous() {
5527 let producer = origin(1).produce();
5528 let consumer = producer.consume();
5529
5530 let anonymous = producer
5531 .dynamic("room", Route::default().with_hops(hops(&[0])).with_cost(1))
5532 .unwrap();
5533 let identified = producer
5534 .dynamic("room", Route::default().with_hops(hops(&[10])).with_cost(5))
5535 .unwrap();
5536
5537 let _pending = consumer.request_broadcast("room/alice");
5538 let request = queued(&identified).await;
5539 assert_eq!(request.path().as_str(), "room/alice");
5540 assert!(
5541 anonymous.poll_requested_broadcast(&kio::Waiter::noop()).is_pending(),
5542 "the cheaper anonymous route must not serve"
5543 );
5544 }
5545
5546 #[tokio::test]
5547 async fn update_reprices_in_place() {
5548 let producer = origin(1).produce();
5549 let mut announced = producer.consume().announced();
5550
5551 let announcement = producer.announce("room", Route::default()).unwrap();
5552 announced.assert_next_active("room");
5553
5554 announcement.update(Route::default().with_cost(9)).unwrap();
5555 let route = announced.assert_next_active("room");
5556 assert_eq!(route.cost, Cost::new(9));
5557 }
5558
5559 #[tokio::test]
5560 async fn retract_after_undelivered_reprice_still_delivered() {
5561 let producer = origin(1).produce();
5562 let mut announced = producer.consume().announced();
5563
5564 let announcement = producer.announce("room", Route::default()).unwrap();
5565 announced.assert_next_active("room");
5566
5567 announcement.update(Route::default().with_cost(9)).unwrap();
5571 drop(announcement);
5572 announced.assert_next_ended("room");
5573 announced.assert_next_wait();
5574 }
5575
5576 #[tokio::test]
5577 async fn scoped_cursor_advertises_most_specific_covering_route() {
5578 let producer = origin(1).produce();
5579 let _broad = producer.announce("room", Route::default().with_cost(1)).unwrap();
5582 let _narrow = producer.announce("room/alice", Route::default().with_cost(9)).unwrap();
5583
5584 let consumer = producer
5585 .consume()
5586 .scope("room/alice", &Patterns::from(Pattern::all()))
5587 .unwrap();
5588 let mut announced = consumer.announced();
5589 let route = announced.assert_next_active("");
5590 assert_eq!(route.cost, Cost::new(9));
5591 announced.assert_next_wait();
5592 }
5593
5594 #[tokio::test]
5595 async fn capture_change_retracts_before_reannouncing_a_presented_prefix() {
5596 let producer = origin(1).produce();
5597 let _broad = producer.announce("room", Route::default()).unwrap();
5598 let exact = producer.announce("room/alice", Route::default()).unwrap();
5599 let consumer = producer
5600 .consume()
5601 .scope("", &Patterns::from("room/*".parse::<Pattern>().unwrap()))
5602 .unwrap()
5603 .scope("room/alice", &Patterns::from(Pattern::all()))
5604 .unwrap();
5605 let mut announced = consumer.announced();
5606
5607 let first = announced.next().now_or_never().expect("next").expect("announce");
5608 assert_eq!(first.prefix.as_str(), "");
5609 assert_eq!(first.kind, AnnounceKind::Announced);
5610 assert_eq!(first.captures, Some(Vec::new()));
5611
5612 drop(exact);
5613 let retracted = announced.next().now_or_never().expect("next").expect("retract");
5614 assert_eq!(retracted.prefix.as_str(), "");
5615 assert_eq!(retracted.kind, AnnounceKind::Retracted);
5616 assert_eq!(retracted.captures, Some(Vec::new()));
5617 let replacement = announced.next().now_or_never().expect("next").expect("announce");
5618 assert_eq!(replacement.prefix.as_str(), "");
5619 assert_eq!(replacement.kind, AnnounceKind::Announced);
5620 assert_eq!(replacement.captures, None);
5621 }
5622
5623 #[tokio::test]
5624 async fn routed_broadcast_resolves_once_announced() {
5625 let producer = origin(1).produce();
5626 let consumer = producer.consume();
5627
5628 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
5630 assert!((&mut resolving).now_or_never().is_none());
5631
5632 let broadcast = producer.create_broadcast("room/alice").unwrap();
5634 for _ in 0..20 {
5635 tokio::task::yield_now().await;
5636 }
5637 assert!((&mut resolving).now_or_never().is_none());
5638
5639 broadcast.announce(Route::default()).unwrap();
5640 let resolved = resolving.await.expect("resolves once announced");
5641 assert_eq!(resolved.info().path.as_str(), "room/alice");
5642 drop(broadcast);
5643 }
5644
5645 #[tokio::test]
5648 async fn cheaper_remote_route_beats_a_local_broadcast() {
5649 let producer = origin(1).produce();
5650 let consumer = producer.consume();
5651 let mut announced = consumer.announced();
5652
5653 let _local = producer.publish("room/alice", Route::default().with_cost(5)).unwrap();
5654 assert_eq!(announced.assert_next_active("room/alice").cost, Cost::new(5));
5655
5656 let server = producer
5657 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(1))
5658 .unwrap();
5659 let route = announced.assert_next_active("room/alice");
5660 assert_eq!(route.cost, Cost::new(1));
5661 assert_eq!(route.hops, hops(&[10]));
5662
5663 let pending = consumer.request_broadcast("room/alice");
5665 let request = queued(&server).await;
5666 let upstream = broadcast::Info::new().produce();
5667 request.accept(&upstream);
5668 pending.await.expect("resolves through the cheaper route");
5669 }
5670
5671 #[tokio::test]
5675 async fn cheaper_route_after_a_front_wins_new_requests() {
5676 let producer = origin(1).produce();
5677 let consumer = producer.consume();
5678
5679 let _local = producer.publish("room/alice", Route::default().with_cost(5)).unwrap();
5680 let first = consumer
5681 .request_broadcast("room/alice")
5682 .await
5683 .expect("resolves locally");
5684
5685 let server = producer
5686 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(1))
5687 .unwrap();
5688
5689 let pending = consumer.request_broadcast("room/alice");
5690 let request = queued(&server).await;
5691 let upstream = broadcast::Info::new().produce();
5692 request.accept(&upstream);
5693 let second = pending.await.expect("resolves through the cheaper route");
5694
5695 assert!(!first.is_closed(), "the old front must keep serving its readers");
5696 assert!(!first.is_clone(&second), "the newcomer must not join the old front");
5697 }
5698
5699 #[tokio::test]
5703 async fn local_broadcast_wins_a_tie_with_a_hopless_route() {
5704 let producer = origin(1).produce();
5705 let consumer = producer.consume();
5706
5707 let _local = producer.publish("room/alice", Route::default()).unwrap();
5708 let server = producer.dynamic("room/alice", Route::default()).unwrap();
5709
5710 let resolved = tokio::time::timeout(Duration::from_secs(1), consumer.request_broadcast("room/alice"))
5712 .await
5713 .expect("the newer hopless route won the tie")
5714 .expect("resolves");
5715 assert_eq!(resolved.info().path.as_str(), "room/alice");
5716 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
5717 }
5718
5719 #[tokio::test]
5722 async fn announce_stats_follow_the_advertisement() {
5723 let registry = stats::Registry::new(stats::Config::new());
5724 let producer = origin(1)
5725 .produce()
5726 .with_stats(registry.tier(stats::Tier::default()).session("root"));
5727 let announces = || {
5728 registry
5729 .snapshot()
5730 .traffic()
5731 .into_iter()
5732 .find(|(_, role, _)| *role == stats::Role::Subscriber)
5733 .map(|(_, _, traffic)| (traffic.announces_started, traffic.announces_ended))
5734 .unwrap_or_default()
5735 };
5736
5737 let broadcast = producer.create_broadcast("room/alice").unwrap();
5738 assert_eq!(announces(), (0, 0), "a hidden broadcast is not announced");
5739 broadcast.announce(Route::default()).unwrap();
5740 broadcast
5741 .announce(Route {
5742 cost: Cost::new(3),
5743 ..Route::default()
5744 })
5745 .unwrap();
5746 assert_eq!(announces(), (1, 0), "a re-price is not another announce");
5747 broadcast.unannounce();
5748 assert_eq!(announces(), (1, 1));
5749 broadcast.announce(Route::default()).unwrap();
5750 drop(broadcast);
5751 assert_eq!(announces(), (2, 2));
5752 }
5753
5754 #[tokio::test]
5756 async fn local_broadcast_wins_a_cost_tie() {
5757 let producer = origin(1).produce();
5758 let consumer = producer.consume();
5759 let mut announced = consumer.announced();
5760
5761 let server = producer
5762 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(2))
5763 .unwrap();
5764 announced.assert_next_active("room/alice");
5765 let _local = producer.publish("room/alice", Route::default().with_cost(2)).unwrap();
5766 assert!(announced.assert_next_active("room/alice").hops.is_empty());
5767
5768 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
5769 assert_eq!(resolved.info().path.as_str(), "room/alice");
5770 for _ in 0..20 {
5771 tokio::task::yield_now().await;
5772 }
5773 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
5774 }
5775
5776 #[tokio::test]
5779 async fn unannounce_ends_the_front() {
5780 let producer = origin(1).produce();
5781 let consumer = producer.consume();
5782
5783 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
5784 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
5785
5786 broadcast.unannounce();
5787 let err = consumer.request_broadcast("room/alice").await.err().unwrap();
5788 assert!(matches!(err, Error::Unroutable), "joined a retracted front: {err}");
5789 settle(|| resolved.is_closed()).await;
5790 assert!(!broadcast.consume().is_closed(), "the broadcast itself lives on");
5791
5792 broadcast.announce(Route::default()).unwrap();
5794 let again = consumer.request_broadcast("room/alice").await.expect("resolves again");
5795 assert!(!again.is_clone(&resolved));
5796 }
5797
5798 #[tokio::test]
5802 async fn unannounce_keeps_a_track_awaiting_its_info() {
5803 let producer = origin(1).produce();
5804 let consumer = producer.consume();
5805
5806 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
5807 let mut dynamic = broadcast.dynamic();
5808 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
5809 let track = resolved.track("video").unwrap();
5810 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
5811 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
5812 .await
5813 .expect("the front asked the source")
5814 .expect("request");
5815
5816 broadcast.unannounce();
5817 settle(|| resolved.is_closed()).await;
5818
5819 let source = request.accept(None);
5820 let mut group = source.append_group().unwrap();
5821 group.write_frame(crate::Timestamp::ZERO, b"late".as_ref()).unwrap();
5822 group.finish().unwrap();
5823 source.finish().unwrap();
5824
5825 let mut subscription = subscribing.await.unwrap().expect("subscribe survives the retraction");
5826 let mut group = subscription.recv_group().await.unwrap().expect("the source's group");
5827 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"late");
5828 assert!(matches!(subscription.recv_group().await, Ok(None)), "ends cleanly");
5829 }
5830
5831 async fn served_front() -> (Dynamic, broadcast::Producer, broadcast::Dynamic, broadcast::Consumer) {
5834 let producer = origin(1).produce();
5835 let consumer = producer.consume();
5836 let server = producer
5837 .dynamic("room/alice", Route::default().with_hops(hops(&[10])))
5838 .unwrap();
5839 let pending = consumer.request_broadcast("room/alice");
5840 let upstream = broadcast::Info::new().produce();
5841 let dynamic = upstream.dynamic();
5842 queued(&server).await.accept(&upstream);
5843 let resolved = pending.await.expect("resolves");
5844 (server, upstream, dynamic, resolved)
5845 }
5846
5847 #[tokio::test]
5852 async fn returning_reader_skips_a_warm_cache_the_copy_resolved_past() {
5853 let ms = |v: u64| crate::Timestamp::from_millis(v).unwrap();
5854 let (_server, _upstream, mut dynamic, resolved) = served_front().await;
5855 let budget = track::Subscription::default().with_max_age(Duration::from_millis(100));
5856
5857 let track = resolved.track("audio").unwrap();
5858 let b = budget.clone();
5859 let subscribing = tokio::spawn(async move { track.subscribe(b).await });
5860 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
5861 .await
5862 .expect("the front asked the source")
5863 .expect("request");
5864 let source = request.resolving_start().accept(None);
5865 for seq in 0..4u64 {
5866 let mut group = source.create_group(seq.into()).unwrap();
5867 group.write_frame(ms(seq * 20), b"old".as_ref()).unwrap();
5868 group.finish().unwrap();
5869 }
5870 let mut subscription = subscribing.await.unwrap().expect("subscribe");
5871 subscription.recv_group().await.unwrap().expect("the live group");
5872 drop(subscription);
5873 tokio::time::timeout(Duration::from_secs(1), source.unused())
5874 .await
5875 .expect("parked")
5876 .expect("source open");
5877 drop(source);
5878
5879 let track = resolved.track("audio").unwrap();
5880 let subscribing = tokio::spawn(async move { track.subscribe(budget).await });
5881 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
5882 .await
5883 .expect("the front asked the source again")
5884 .expect("request");
5885 let mut source = request.resolving_start().accept(None);
5886 let mut subscription = subscribing.await.unwrap().expect("resubscribe");
5887
5888 assert!(
5890 tokio::time::timeout(Duration::from_millis(50), subscription.recv_group())
5891 .await
5892 .is_err(),
5893 "the warm cache was served before the copy resolved its start"
5894 );
5895
5896 source.start_at(20).unwrap();
5898 let mut group = source.create_group(20u64.into()).unwrap();
5899 group.write_frame(ms(2000), b"new".as_ref()).unwrap();
5900 group.finish().unwrap();
5901 let group = subscription.recv_group().await.unwrap().expect("the live group");
5902 assert_eq!(group.sequence, 20, "a stale warm group was served");
5903 }
5904
5905 #[tokio::test]
5910 async fn returning_reader_replays_a_current_warm_cache() {
5911 let (_server, _upstream, mut dynamic, resolved) = served_front().await;
5912
5913 let track = resolved.track("catalog").unwrap();
5914 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
5915 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
5916 .await
5917 .expect("the front asked the source")
5918 .expect("request");
5919 let source = request.resolving_start().accept(None);
5920 let mut group = source.create_group(0u64.into()).unwrap();
5921 group.write_frame(crate::Timestamp::ZERO, b"snapshot".as_ref()).unwrap();
5922 group.finish().unwrap();
5923 let mut subscription = subscribing.await.unwrap().expect("subscribe");
5924 subscription.recv_group().await.unwrap().expect("the catalog");
5925 drop(subscription);
5926 tokio::time::timeout(Duration::from_secs(1), source.unused())
5927 .await
5928 .expect("parked")
5929 .expect("source open");
5930 drop(source);
5931
5932 let track = resolved.track("catalog").unwrap();
5933 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
5934 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
5935 .await
5936 .expect("the front asked the source again")
5937 .expect("request");
5938 let mut source = request.resolving_start().accept(None);
5939 let mut subscription = subscribing.await.unwrap().expect("resubscribe");
5940
5941 let reading = tokio::spawn(async move {
5944 let mut group = subscription.recv_group().await.unwrap().expect("the catalog");
5945 assert_eq!(group.sequence, 0);
5946 group.read_frame().await.unwrap().expect("the snapshot").payload
5947 });
5948 tokio::task::yield_now().await;
5949 assert_eq!(
5950 source.subscription().and_then(|sub| sub.start),
5951 Some(track::Position { group: 0, frame: 1 }),
5952 "the re-splice asked past the cached catalog"
5953 );
5954 source.start_at(0).unwrap();
5955 let mut tail = source.create_group(0u64.into()).unwrap();
5956 tail.start_at(1).unwrap();
5957 tail.finish().unwrap();
5958 let payload = tokio::time::timeout(Duration::from_secs(1), reading)
5959 .await
5960 .expect("the returning reader never got the catalog")
5961 .unwrap();
5962 assert_eq!(&payload[..], b"snapshot");
5963 }
5964
5965 #[tokio::test(start_paused = true)]
5969 async fn finished_track_is_forgotten_after_the_linger() {
5970 let (_server, _upstream, mut dynamic, resolved) = served_front().await;
5971
5972 let track = resolved.track("catalog").unwrap();
5973 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
5974 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
5975 .await
5976 .expect("the front asked the source")
5977 .expect("request");
5978 let source = request.resolving_start().accept(None);
5979 let mut group = source.create_group(0u64.into()).unwrap();
5980 group.write_frame(crate::Timestamp::ZERO, b"snapshot".as_ref()).unwrap();
5981 group.finish().unwrap();
5982 source.finish().unwrap();
5983 let mut subscription = subscribing.await.unwrap().expect("subscribe");
5984 assert_eq!(
5985 next_group(&mut subscription)
5986 .await
5987 .unwrap()
5988 .expect("the catalog")
5989 .sequence,
5990 0
5991 );
5992 assert!(next_group(&mut subscription).await.unwrap().is_none());
5993 drop(subscription);
5994 drop(source);
5995
5996 let mut subscription = resolved.track("catalog").unwrap().subscribe(None).await.unwrap();
5998 assert_eq!(
5999 next_group(&mut subscription)
6000 .await
6001 .unwrap()
6002 .expect("the catalog")
6003 .sequence,
6004 0
6005 );
6006 assert!(next_group(&mut subscription).await.unwrap().is_none());
6007 drop(subscription);
6008 assert!(
6009 tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
6010 .await
6011 .is_err(),
6012 "a finished track within the linger asked the source again"
6013 );
6014
6015 tokio::time::sleep(TRACK_IDLE_LINGER).await;
6017
6018 let track = resolved.track("catalog").unwrap();
6019 let _subscribing = tokio::spawn(async move { track.subscribe(None).await });
6020 tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
6021 .await
6022 .expect("the finished track outlived the linger")
6023 .expect("request");
6024 }
6025
6026 #[tokio::test]
6031 async fn returning_reader_continues_an_open_warm_group() {
6032 let (_server, _upstream, mut dynamic, resolved) = served_front().await;
6033
6034 async fn read(group: &mut group::Consumer) -> Vec<u8> {
6035 let frame = tokio::time::timeout(Duration::from_secs(1), group.read_frame())
6036 .await
6037 .expect("frame")
6038 .unwrap()
6039 .expect("group ended");
6040 frame.payload.to_vec()
6041 }
6042
6043 let mut expect: Vec<&[u8]> = Vec::new();
6044 let mut floor: Option<track::Position> = None;
6045 for (round, payload) in [b"a".as_ref(), b"b", b"c"].into_iter().enumerate() {
6046 let track = resolved.track("log").unwrap();
6047 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6048 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
6049 .await
6050 .expect("the front asked the source")
6051 .expect("request");
6052 let mut source = request.resolving_start().accept(None);
6053 let mut subscription = subscribing.await.unwrap().expect("subscribe");
6054
6055 source.start_at(0).unwrap();
6057 let mut group = source.create_group(0u64.into()).unwrap();
6058 if let Some(floor) = floor {
6059 group.start_at(floor.frame).unwrap();
6060 }
6061 group.write_frame(crate::Timestamp::ZERO, payload).unwrap();
6062 expect.push(payload);
6063 source
6064 .insert_datagram(10, crate::Timestamp::ZERO, b"datagram".as_ref())
6065 .unwrap();
6066
6067 let mut reading = tokio::time::timeout(Duration::from_secs(1), subscription.recv_group())
6068 .await
6069 .expect("group 0")
6070 .unwrap()
6071 .expect("track ended");
6072 assert_eq!(reading.sequence, 0);
6073 for frame in &expect {
6074 assert_eq!(read(&mut reading).await, *frame, "round {round}");
6075 }
6076 assert_eq!(
6077 source.subscription().and_then(|sub| sub.start),
6078 floor,
6079 "round {round} asked for the wrong continuation"
6080 );
6081
6082 drop(reading);
6083 drop(subscription);
6084 tokio::time::timeout(Duration::from_secs(1), source.unused())
6085 .await
6086 .expect("parked")
6087 .expect("source open");
6088 drop(group);
6089 drop(source);
6090 floor = Some(track::Position {
6091 group: 0,
6092 frame: expect.len() as u64,
6093 });
6094 }
6095 }
6096
6097 #[tokio::test]
6104 async fn a_takeover_keeps_the_open_group_head_for_later_readers() {
6105 tokio::time::pause();
6106 for dies_first in [true, false] {
6107 takeover_keeps_the_open_group_head(dies_first).await;
6108 }
6109 }
6110
6111 async fn takeover_keeps_the_open_group_head(dies_first: bool) {
6112 let producer = origin(1).produce();
6113 let first_server = producer
6114 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(5))
6115 .unwrap();
6116 let pending = producer.consume().request_broadcast("room/alice");
6117 let upstream = broadcast::Info::new().produce();
6118 let mut dynamic = upstream.dynamic();
6119 queued(&first_server).await.accept(&upstream);
6120 let resolved = pending.await.unwrap();
6121
6122 async fn read(group: &mut group::Consumer) -> Vec<u8> {
6123 let frame = tokio::time::timeout(Duration::from_secs(1), group.read_frame())
6124 .await
6125 .expect("frame")
6126 .unwrap()
6127 .expect("group ended");
6128 frame.payload.to_vec()
6129 }
6130
6131 let track = resolved.track("log").unwrap();
6133 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6134 let source = dynamic.requested_track().await.unwrap().accept(None);
6135 let mut group = source.create_group(0u64.into()).unwrap();
6136 group.write_frame(crate::Timestamp::ZERO, b"a".as_ref()).unwrap();
6137 group.write_frame(crate::Timestamp::ZERO, b"b".as_ref()).unwrap();
6138 let mut subscription = subscribing.await.unwrap().unwrap();
6139 let mut reading = subscription.recv_group().await.unwrap().unwrap();
6140 assert_eq!(read(&mut reading).await, b"a");
6141 assert_eq!(read(&mut reading).await, b"b");
6142
6143 let mut first = Some((group, source, upstream, dynamic));
6146 let mut joining = None;
6147 if dies_first {
6148 drop(first.take());
6149 assert!(futures::FutureExt::now_or_never(subscription.recv_group()).is_none());
6152 let track = resolved.track("log").unwrap();
6153 joining = Some(tokio::spawn(async move {
6154 let mut joined = track.subscribe(None).await.unwrap();
6155 let group = joined.recv_group().await.unwrap().expect("track ended");
6156 (joined, group)
6157 }));
6158 }
6159 let cost = if dies_first { 5 } else { 0 };
6160 let replacement_server = producer
6161 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(cost))
6162 .unwrap();
6163 let replacement = broadcast::Info::new().produce();
6164 let mut replacement_dynamic = replacement.dynamic();
6165 queued(&replacement_server).await.accept(&replacement);
6166 let mut next = tokio::time::timeout(Duration::from_secs(1), replacement_dynamic.requested_track())
6167 .await
6168 .expect("the front asked the replacement")
6169 .unwrap()
6170 .resolving_start()
6171 .accept(None);
6172 next.start_at(0).unwrap();
6173 let mut next_group = next.create_group(0u64.into()).unwrap();
6174 next_group.start_at(2).unwrap();
6175 next_group.write_frame(crate::Timestamp::ZERO, b"c".as_ref()).unwrap();
6176 assert_eq!(read(&mut reading).await, b"c", "dies_first={dies_first}");
6177
6178 if let Some((group, ..)) = &mut first {
6180 let mut independent = group.consume();
6181 group.write_frame(crate::Timestamp::ZERO, b"x".as_ref()).unwrap();
6182 for expect in [b"a", b"b", b"x"] {
6183 assert_eq!(read(&mut independent).await, expect);
6184 }
6185 group.finish().unwrap();
6186 }
6187 drop(first);
6188
6189 let track = resolved.track("log").unwrap();
6190 let mut later = tokio::time::timeout(Duration::from_secs(1), track.subscribe(None))
6191 .await
6192 .expect("subscribe")
6193 .unwrap();
6194 let mut late = tokio::time::timeout(Duration::from_secs(1), later.recv_group())
6195 .await
6196 .unwrap_or_else(|_| panic!("dies_first={dies_first}: the later reader never got the group"))
6197 .unwrap()
6198 .expect("track ended");
6199 assert_eq!(late.sequence, 0);
6200 for expect in [b"a", b"b", b"c"] {
6201 assert_eq!(read(&mut late).await, expect, "dies_first={dies_first}");
6202 }
6203 let mut joined = None;
6204 if let Some(joining) = joining {
6205 let (subscription, mut group) = tokio::time::timeout(Duration::from_secs(1), joining)
6206 .await
6207 .expect("the reader joining mid-outage never got the group")
6208 .unwrap();
6209 assert_eq!(group.sequence, 0);
6210 for expect in [b"a", b"b", b"c"] {
6211 assert_eq!(read(&mut group).await, expect);
6212 }
6213 joined = Some((subscription, group));
6214 }
6215
6216 assert!(
6218 tokio::time::timeout(Duration::from_secs(1), subscription.recv_group())
6219 .await
6220 .is_err(),
6221 "dies_first={dies_first}: the group was handed out twice"
6222 );
6223
6224 next_group.write_frame(crate::Timestamp::ZERO, b"d".as_ref()).unwrap();
6226 assert_eq!(read(&mut reading).await, b"d");
6227 assert_eq!(read(&mut late).await, b"d");
6228 if let Some((_, group)) = &mut joined {
6229 assert_eq!(read(group).await, b"d");
6230 }
6231 next_group.finish().unwrap();
6232 drop((
6233 joined,
6234 reading,
6235 late,
6236 subscription,
6237 later,
6238 next,
6239 replacement,
6240 replacement_server,
6241 first_server,
6242 ));
6243 }
6244
6245 #[tokio::test]
6250 async fn returning_reader_gets_a_live_open_warm_group() {
6251 use futures::FutureExt;
6252
6253 tokio::time::pause();
6254 let (_server, _upstream, mut dynamic, resolved) = served_front().await;
6255
6256 let track = resolved.track("video").unwrap();
6258 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6259 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
6260 .await
6261 .expect("the front asked the source")
6262 .expect("request");
6263 let mut source = request.resolving_start().accept(None);
6264 let mut subscription = subscribing.await.unwrap().expect("subscribe");
6265 source.start_at(0).unwrap();
6266 let mut group = source.create_group(0u64.into()).unwrap();
6267 group.write_frame(crate::Timestamp::ZERO, b"a".as_ref()).unwrap();
6268 let mut reading = subscription.recv_group().await.unwrap().expect("group 0");
6269 assert_eq!(reading.read_frame().await.unwrap().unwrap().payload.as_ref(), b"a");
6270 drop(reading);
6271 drop(subscription);
6272 tokio::time::timeout(Duration::from_secs(1), source.unused())
6273 .await
6274 .expect("parked")
6275 .expect("source open");
6276 drop(group);
6277 drop(source);
6278
6279 let track = resolved.track("video").unwrap();
6281 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6282 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
6283 .await
6284 .expect("the front asked the source")
6285 .expect("request");
6286 let mut source = request.resolving_start().accept(None);
6287 let mut subscription = subscribing.await.unwrap().expect("subscribe");
6288 source.start_at(0).unwrap();
6289 let mut group = source.create_group(0u64.into()).unwrap();
6290 group.start_at(1).unwrap();
6291 group.write_frame(crate::Timestamp::ZERO, b"b".as_ref()).unwrap();
6292
6293 let mut reading = tokio::time::timeout(Duration::from_secs(1), subscription.recv_group())
6294 .await
6295 .expect("group 0")
6296 .unwrap()
6297 .expect("track ended");
6298 assert_eq!(reading.sequence, 0);
6299 let early = reading.finished().now_or_never();
6300 assert!(
6301 early.is_none(),
6302 "the live group already resolved its end before a frame was read: {early:?}"
6303 );
6304
6305 group.finish().unwrap();
6307 assert_eq!(reading.finished().await.unwrap(), 0);
6308 assert_eq!(reading.read_frame().await.unwrap().unwrap().payload.as_ref(), b"a");
6309 assert_eq!(reading.finished().await.unwrap(), 1);
6310 assert_eq!(reading.read_frame().await.unwrap().unwrap().payload.as_ref(), b"b");
6311 assert_eq!(reading.finished().await.unwrap(), 2);
6312 }
6313
6314 #[tokio::test]
6315 async fn warm_head_survives_another_takeover_before_park() {
6316 tokio::time::pause();
6317 let producer = origin(1).produce();
6318 let _server = producer
6319 .dynamic("room/alice", Route::default().with_hops(hops(&[10])).with_cost(5))
6320 .unwrap();
6321 let pending = producer.consume().request_broadcast("room/alice");
6322 let upstream = broadcast::Info::new().produce();
6323 let mut dynamic = upstream.dynamic();
6324 queued(&_server).await.accept(&upstream);
6325 let resolved = pending.await.unwrap();
6326
6327 let track = resolved.track("log").unwrap();
6328 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6329 let source = dynamic.requested_track().await.unwrap().accept(None);
6330 let mut group = source.create_group(0u64.into()).unwrap();
6331 group.write_frame(crate::Timestamp::ZERO, b"a".as_ref()).unwrap();
6332 group.write_frame(crate::Timestamp::ZERO, b"b".as_ref()).unwrap();
6333 let mut subscription = subscribing.await.unwrap().unwrap();
6334 let mut reading = subscription.recv_group().await.unwrap().unwrap();
6335 assert_eq!(&reading.read_frame().await.unwrap().unwrap().payload[..], b"a");
6336 drop(reading);
6337 drop(subscription);
6338 source.unused().await.unwrap();
6339 drop(group);
6340 drop(source);
6341
6342 let track = resolved.track("log").unwrap();
6343 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6344 let mut resumed = dynamic.requested_track().await.unwrap().resolving_start().accept(None);
6345 resumed.start_at(0).unwrap();
6346 let mut subscription = subscribing.await.unwrap().unwrap();
6347 let mut reading = subscription.recv_group().await.unwrap().unwrap();
6348 assert_eq!(&reading.read_frame().await.unwrap().unwrap().payload[..], b"a");
6349
6350 let replacement_server = producer
6351 .dynamic("room/alice", Route::default().with_hops(hops(&[10])))
6352 .unwrap();
6353 let replacement = broadcast::Info::new().produce();
6354 let mut replacement_dynamic = replacement.dynamic();
6355 queued(&replacement_server).await.accept(&replacement);
6356 let mut source = replacement_dynamic
6357 .requested_track()
6358 .await
6359 .unwrap()
6360 .resolving_start()
6361 .accept(None);
6362 source.start_at(0).unwrap();
6363 let mut group = source.create_group(0u64.into()).unwrap();
6364 group.start_at(2).unwrap();
6365 group.write_frame(crate::Timestamp::ZERO, b"c".as_ref()).unwrap();
6366 let mut continuation = reading.clone();
6367 continuation.start_at(2);
6368 let frame = tokio::time::timeout(Duration::from_secs(1), continuation.read_frame())
6369 .await
6370 .expect("replacement frame")
6371 .unwrap()
6372 .unwrap();
6373 assert_eq!(&frame.payload[..], b"c");
6374 drop(continuation);
6375 assert_eq!(&reading.read_frame().await.unwrap().unwrap().payload[..], b"b");
6376 assert_eq!(&reading.read_frame().await.unwrap().unwrap().payload[..], b"c");
6377 drop(reading);
6378 drop(subscription);
6379 source.unused().await.unwrap();
6380 drop(group);
6381 drop(source);
6382
6383 let track = resolved.track("log").unwrap();
6384 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6385 let mut next = replacement_dynamic
6386 .requested_track()
6387 .await
6388 .unwrap()
6389 .resolving_start()
6390 .accept(None);
6391 next.start_at(0).unwrap();
6392 let mut subscription = subscribing.await.unwrap().unwrap();
6393 let mut reading = subscription.recv_group().await.unwrap().unwrap();
6394 for expected in [b"a", b"b", b"c"] {
6395 assert_eq!(&reading.read_frame().await.unwrap().unwrap().payload[..], expected);
6396 }
6397 }
6398
6399 #[tokio::test]
6402 async fn unannounce_keeps_a_returning_reader_awaiting_its_info() {
6403 let producer = origin(1).produce();
6404 let consumer = producer.consume();
6405
6406 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
6407 let mut dynamic = broadcast.dynamic();
6408 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
6409 let track = resolved.track("video").unwrap();
6410 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6411 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
6412 .await
6413 .expect("the front asked the source")
6414 .expect("request");
6415 let source = request.accept(None);
6416 let mut group = source.append_group().unwrap();
6417 group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap();
6418 group.finish().unwrap();
6419 let mut subscription = subscribing.await.unwrap().expect("subscribe");
6420 subscription.recv_group().await.unwrap().expect("the cached group");
6421 drop(subscription);
6422
6423 tokio::time::timeout(Duration::from_secs(1), source.unused())
6426 .await
6427 .expect("parked")
6428 .expect("source open");
6429 drop(source);
6430 let track = resolved.track("video").unwrap();
6431 let subscribing = tokio::spawn(async move { track.subscribe(None).await });
6432 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
6433 .await
6434 .expect("the front asked the source again")
6435 .expect("request");
6436
6437 broadcast.unannounce();
6438 settle(|| resolved.is_closed()).await;
6439
6440 let source = request.accept(None);
6442 let mut group = source.create_group(1u64.into()).unwrap();
6443 group.write_frame(crate::Timestamp::ZERO, b"late".as_ref()).unwrap();
6444 group.finish().unwrap();
6445 source.finish().unwrap();
6446
6447 let mut subscription = subscribing.await.unwrap().expect("subscribe survives the retraction");
6448 let mut payloads = Vec::new();
6449 while let Some(mut group) = subscription.recv_group().await.expect("ends cleanly") {
6450 payloads.push(group.read_frame().await.unwrap().unwrap().payload);
6451 }
6452 assert_eq!(payloads.last().map(|p| &p[..]), Some(&b"late"[..]));
6453 }
6454
6455 #[tokio::test]
6458 async fn reannounce_before_the_front_acts_keeps_it() {
6459 let producer = origin(1).produce();
6460 let consumer = producer.consume();
6461
6462 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
6463 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
6464
6465 broadcast.unannounce();
6466 broadcast.announce(Route::default()).unwrap();
6467 for _ in 0..20 {
6468 tokio::task::yield_now().await;
6469 }
6470 assert!(!resolved.is_closed(), "the front ended across a reannouncement");
6471 let again = consumer.request_broadcast("room/alice").await.expect("resolves");
6472 assert!(again.is_clone(&resolved));
6473 }
6474
6475 #[tokio::test]
6476 async fn local_broadcast_resolves_once_announced() {
6477 let producer = origin(1).produce();
6478 let consumer = producer.consume();
6479
6480 let broadcast = producer.create_broadcast("room/alice").unwrap();
6482 let err = consumer
6483 .request_broadcast("room/alice")
6484 .now_or_never()
6485 .expect("unroutable is synchronous")
6486 .err()
6487 .unwrap();
6488 assert!(matches!(err, Error::Unroutable));
6489
6490 broadcast.announce(Route::default()).unwrap();
6491 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
6492 assert_eq!(resolved.info().path.as_str(), "room/alice");
6493 drop(broadcast);
6494
6495 let err = consumer
6497 .request_broadcast("room/bob")
6498 .now_or_never()
6499 .expect("unroutable is synchronous")
6500 .err()
6501 .unwrap();
6502 assert!(matches!(err, Error::Unroutable));
6503 }
6504
6505 #[test]
6506 fn create_broadcast_accepts_a_max_depth_path() {
6507 let producer = origin(1).produce();
6508 let path = vec!["a"; Path::MAX_PARTS].join("/");
6509 let _broadcast = producer.create_broadcast(path.as_str()).expect("max depth is allowed");
6510 let deeper = vec!["a"; Path::MAX_PARTS + 1].join("/");
6511 assert!(matches!(
6512 producer.create_broadcast(deeper.as_str()),
6513 Err(Error::BoundsExceeded(_))
6514 ));
6515 }
6516
6517 #[tokio::test]
6518 async fn duplicate_routes_aggregate_until_the_last_leaves() {
6519 let producer = origin(1).produce();
6520 let first = producer.dynamic("live", Route::default().with_cost(3)).unwrap();
6521 let second = producer.dynamic("live", Route::default().with_cost(1)).unwrap();
6522
6523 let mut announced = producer.consume().announced();
6524 let update = announced.next().now_or_never().expect("next").expect("no next");
6525 assert_eq!(update.prefix.as_str(), "live");
6526 assert_eq!(update.kind, AnnounceKind::Announced);
6527 assert_eq!(update.route.cost, Cost::new(1));
6528 announced.assert_next_wait();
6529
6530 drop(second);
6531 let update = announced.next().now_or_never().expect("next").expect("no next");
6532 assert_eq!(update.prefix.as_str(), "live");
6533 assert_eq!(update.kind, AnnounceKind::Updated);
6534 assert_eq!(update.route.cost, Cost::new(3));
6535
6536 drop(first);
6537 announced.assert_next_ended("live");
6538 announced.assert_next_wait();
6539 }
6540
6541 #[test]
6542 fn dynamic_may_cover_a_scope_but_disjoint_prefixes_are_refused() {
6543 let producer = origin(1).produce();
6544 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
6545 let _broad = scoped
6546 .dynamic("", Route::default())
6547 .expect("an overlapping prefix is accepted");
6548
6549 let _ok = scoped
6550 .dynamic("room/alice", Route::default())
6551 .expect("a contained prefix is accepted");
6552 assert!(matches!(
6553 scoped.dynamic("other", Route::default()),
6554 Err(Error::Unauthorized)
6555 ));
6556 }
6557
6558 #[tokio::test]
6559 async fn dynamic_route_keeps_its_producer_scope() {
6560 let producer = origin(1).produce();
6561 let scope = Patterns::from("*/chat".parse::<Pattern>().unwrap());
6562 let scoped = producer.scope("", &scope).unwrap();
6563 let dynamic = scoped.dynamic("", Route::default()).unwrap();
6564
6565 let mut matching = producer
6566 .consume()
6567 .scope("", &scopes(&["room/chat"]))
6568 .unwrap()
6569 .announced();
6570 matching.assert_next_active("");
6571 let mut outside = producer
6572 .consume()
6573 .scope("", &scopes(&["room/video"]))
6574 .unwrap()
6575 .announced();
6576 outside.assert_next_wait();
6577
6578 let refused = producer
6579 .consume()
6580 .request_broadcast("room/video")
6581 .now_or_never()
6582 .expect("an out-of-scope request must be refused synchronously");
6583 assert!(matches!(refused, Err(Error::Unroutable)));
6584 assert!(dynamic.requested_broadcast().now_or_never().is_none());
6585
6586 let _pending = producer.consume().request_broadcast("room/chat");
6587 let request = queued(&dynamic).await;
6588 assert_eq!(request.path().as_str(), "room/chat");
6589 }
6590
6591 #[tokio::test]
6592 async fn scoped_cursor_selects_among_the_routes_it_can_see() {
6593 let producer = origin(1).produce();
6594 let scoped = |pattern: &str| {
6595 producer
6596 .scope("", &Patterns::from(pattern.parse::<Pattern>().unwrap()))
6597 .unwrap()
6598 };
6599 let _chat = scoped("*/chat").dynamic("", Route::default().with_cost(1)).unwrap();
6600 let _video = scoped("*/video").dynamic("", Route::default().with_cost(5)).unwrap();
6601
6602 let mut video = producer
6603 .consume()
6604 .scope("", &scopes(&["room/video"]))
6605 .unwrap()
6606 .announced();
6607 assert_eq!(video.assert_next_active("").cost, Cost::new(5));
6608 }
6609
6610 #[tokio::test]
6611 async fn route_changes_preserve_each_cursors_visible_winner() {
6612 let producer = origin(1).produce();
6613 let consumer = producer.consume();
6614 let mut all = consumer.clone().announced();
6615 let mut excluded = consumer.clone().excluding(origin(7)).announced();
6616 let mut video = consumer
6617 .clone()
6618 .scope("", &scopes(&["room/live/video"]))
6619 .unwrap()
6620 .announced();
6621 let mut rooted = consumer
6622 .scope("room/live", &Patterns::from(Pattern::all()))
6623 .unwrap()
6624 .announced();
6625
6626 let chat = producer
6627 .scope("", &scopes(&["room/live/chat"]))
6628 .unwrap()
6629 .dynamic("room/live", Route::default().with_hops(hops(&[7])).with_cost(1))
6630 .unwrap();
6631 assert_eq!(all.assert_next_active("room/live").cost, Cost::new(1));
6632 assert_eq!(rooted.assert_next_active("").cost, Cost::new(1));
6633 excluded.assert_next_wait();
6634 video.assert_next_wait();
6635
6636 let video_route = producer
6637 .scope("", &scopes(&["room/live/video"]))
6638 .unwrap()
6639 .dynamic("room/live", Route::default().with_hops(hops(&[8])).with_cost(5))
6640 .unwrap();
6641 all.assert_next_wait();
6642 rooted.assert_next_wait();
6643 assert_eq!(excluded.assert_next_active("room/live").cost, Cost::new(5));
6644 assert_eq!(video.assert_next_active("room/live").cost, Cost::new(5));
6645
6646 chat.update(Route::default().with_hops(hops(&[7])).with_cost(9))
6647 .unwrap();
6648 assert_eq!(all.assert_next_active("room/live").cost, Cost::new(5));
6649 assert_eq!(rooted.assert_next_active("").cost, Cost::new(5));
6650 excluded.assert_next_wait();
6651 video.assert_next_wait();
6652
6653 drop(video_route);
6654 assert_eq!(all.assert_next_active("room/live").cost, Cost::new(9));
6655 assert_eq!(rooted.assert_next_active("").cost, Cost::new(9));
6656 excluded.assert_next_ended("room/live");
6657 video.assert_next_ended("room/live");
6658 }
6659
6660 #[tokio::test]
6661 async fn root_cursor_keeps_the_more_specific_covering_route() {
6662 let producer = origin(1).produce();
6663 let broad = producer.dynamic("room", Route::default().with_cost(1)).unwrap();
6664 let narrow = producer.dynamic("room/live", Route::default().with_cost(9)).unwrap();
6665 let mut announced = producer
6666 .consume()
6667 .scope("room/live/video", &Patterns::from(Pattern::all()))
6668 .unwrap()
6669 .announced();
6670 assert_eq!(announced.assert_next_active("").cost, Cost::new(9));
6671
6672 broad.update(Route::default().with_cost(0)).unwrap();
6673 announced.assert_next_wait();
6674 narrow.update(Route::default().with_cost(8)).unwrap();
6675 assert_eq!(announced.assert_next_active("").cost, Cost::new(8));
6676 drop(narrow);
6677 assert_eq!(announced.assert_next_active("").cost, Cost::new(0));
6678 }
6679
6680 #[tokio::test]
6681 async fn dynamic_accepts_a_max_depth_prefix() {
6682 let producer = origin(1).produce();
6683 let path = (0..Path::MAX_PARTS)
6684 .map(|i| format!("s{i}"))
6685 .collect::<Vec<_>>()
6686 .join("/");
6687 let mut announced = producer.consume().announced();
6688
6689 let dynamic = producer.dynamic(&path, Route::default()).expect("max depth is allowed");
6690 announced.assert_next_active(&path);
6691
6692 let _pending = producer.consume().request_broadcast(&path);
6693 let request = queued(&dynamic).await;
6694 assert_eq!(request.path().as_str(), path);
6695 }
6696
6697 #[tokio::test]
6698 async fn dynamic_exclusion_skips_routes_through_the_subscriber() {
6699 let producer = origin(1).produce();
6700 let _server = producer
6701 .dynamic("live", Route::default().with_hops(hops(&[7])))
6702 .unwrap();
6703
6704 let mut excluded = producer.consume().excluding(origin(7)).announced();
6705 excluded.assert_next_wait();
6706
6707 let mut clean = producer.consume().excluding(origin(8)).announced();
6708 clean.assert_next_active("live");
6709 }
6710
6711 #[tokio::test]
6713 async fn announce_consumer_is_a_stream() {
6714 use futures::StreamExt;
6715 let producer = origin(1).produce();
6716 let server = producer.dynamic("live", Route::default()).unwrap();
6717 let mut announced = producer.consume().announced();
6718 let update = StreamExt::next(&mut announced)
6719 .now_or_never()
6720 .expect("next")
6721 .expect("no next");
6722 assert_eq!(update.prefix.as_str(), "live");
6723 assert_eq!(update.kind, AnnounceKind::Announced);
6724 assert!(StreamExt::next(&mut announced).now_or_never().is_none());
6725 drop(server);
6726 let update = StreamExt::next(&mut announced)
6727 .now_or_never()
6728 .expect("next")
6729 .expect("no next");
6730 assert_eq!(update.kind, AnnounceKind::Retracted);
6731 }
6732
6733 #[tokio::test]
6734 async fn dynamic_retracts() {
6735 let producer = origin(1).produce();
6736 let server = producer.dynamic("live", Route::default()).unwrap();
6737 let mut announced = producer.consume().announced();
6738 announced.assert_next_active("live");
6739
6740 drop(server);
6741 announced.assert_next_ended("live");
6742 }
6743
6744 #[test]
6745 fn charged_wildcard_cost_accumulates_across_hops() {
6746 let first = Cost::new(4).charged(1);
6747 let second = first.charged(2);
6748 assert_eq!(second, Cost { warm: 7, cold: 7 });
6749 }
6750
6751 #[tokio::test]
6752 async fn local_broadcast_is_invisible_until_announced() {
6753 let producer = origin(1).produce();
6754 let mut local = producer.consume().announced();
6755 let mut peer = producer.consume().excluding(Hop::UNKNOWN).announced();
6756 let broadcast = producer.create_broadcast("room/alice").unwrap();
6757 local.assert_next_wait();
6758 peer.assert_next_wait();
6759
6760 broadcast.announce(Route::default()).unwrap();
6761 local.assert_next_active("room/alice");
6762 peer.assert_next_active("room/alice");
6763
6764 drop(broadcast);
6765 local.assert_next_ended("room/alice");
6766 peer.assert_next_ended("room/alice");
6767 }
6768
6769 #[tokio::test]
6770 async fn served_route_materializes_on_demand() {
6771 let producer = origin(1).produce();
6772 let consumer = producer.consume();
6773
6774 let server = producer.dynamic("room", Route::default()).unwrap();
6775
6776 let pending = consumer.request_broadcast("room/alice");
6777 let request = queued(&server).await;
6778 assert_eq!(request.path().as_str(), "room/alice");
6779
6780 let source = broadcast::Info::new().produce();
6781 request.accept(&source);
6782
6783 let resolved = pending.await.expect("resolves");
6784 assert_eq!(resolved.info().path.as_str(), "room/alice");
6786
6787 let again = consumer.request_broadcast("room/alice").await.expect("resolves");
6789 assert!(again.is_clone(&resolved));
6790 }
6791
6792 #[tokio::test]
6793 async fn served_requests_coalesce() {
6794 let producer = origin(1).produce();
6795 let consumer = producer.consume();
6796 let server = producer.dynamic("room", Route::default()).unwrap();
6797
6798 let first = consumer.request_broadcast("room/alice");
6799 let second = consumer.request_broadcast("room/alice");
6800
6801 let request = queued(&server).await;
6802 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
6804
6805 let source = broadcast::Info::new().produce();
6806 request.accept(&source);
6807
6808 let first = first.await.expect("resolves");
6809 let second = second.await.expect("resolves");
6810 assert!(first.is_clone(&second));
6811 }
6812
6813 #[tokio::test]
6814 async fn retract_rejects_pending_requests() {
6815 let producer = origin(1).produce();
6816 let consumer = producer.consume();
6817 let server = producer.dynamic("room", Route::default()).unwrap();
6818
6819 let pending = consumer.request_broadcast("room/alice");
6820 drop(server);
6821
6822 let err = pending.await.err().unwrap();
6823 assert!(matches!(err, Error::Unroutable));
6824
6825 let err = consumer
6827 .request_broadcast("room/alice")
6828 .now_or_never()
6829 .expect("unroutable")
6830 .err()
6831 .unwrap();
6832 assert!(matches!(err, Error::Unroutable));
6833 }
6834
6835 #[tokio::test]
6836 async fn routed_broadcast_survives_serving_route_retraction() {
6837 let producer = origin(1).produce();
6838 let consumer = producer.consume();
6839
6840 let standby_server = producer.dynamic("room", Route::default()).unwrap();
6843 let second_server = producer.dynamic("room", Route::default()).unwrap();
6844 let incumbent_server = producer.dynamic("room", Route::default()).unwrap();
6845
6846 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
6847 assert!((&mut resolving).now_or_never().is_none());
6848
6849 drop(incumbent_server);
6855 assert!((&mut resolving).now_or_never().is_none());
6856 drop(second_server);
6857 assert!((&mut resolving).now_or_never().is_none());
6858
6859 let request = queued(&standby_server).await;
6860 let source = broadcast::Info::new().produce();
6861 request.accept(&source);
6862
6863 let resolved = resolving.await.expect("resolves via the standby");
6864 assert_eq!(resolved.info().path.as_str(), "room/alice");
6865 }
6866
6867 #[tokio::test]
6868 async fn split_horizon_skips_routes_through_the_requester() {
6869 let producer = origin(1).produce();
6870 let _server = producer
6871 .dynamic("room", Route::default().with_hops(hops(&[7])))
6872 .unwrap();
6873
6874 let excluded = producer.consume().excluding(origin(7));
6876 let err = excluded
6877 .request_broadcast("room/alice")
6878 .now_or_never()
6879 .expect("unroutable")
6880 .err()
6881 .unwrap();
6882 assert!(matches!(err, Error::Unroutable));
6883
6884 let clean = producer.consume().excluding(origin(8));
6886 let pending = clean.request_broadcast("room/alice");
6887 assert!(pending.now_or_never().is_none());
6888 }
6889
6890 #[tokio::test(start_paused = true)]
6894 async fn viewer_sessions_share_the_plain_front() {
6895 let producer = origin(1).produce();
6896 let server = producer
6898 .dynamic("room", Route::default().with_hops(hops(&[10])))
6899 .unwrap();
6900 let upstream = broadcast::Info::new().produce();
6901 let source = upstream.create_track("video", None).unwrap();
6902 let mut group = source.append_group().unwrap();
6903 group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap();
6904
6905 let counts = || {
6906 let table = producer.shared.lock();
6907 let watches = table
6908 .routes
6909 .root
6910 .find(Path::new("room/alice").parts())
6911 .map_or(0, |node| node.watches.len());
6912 (table.fronts.len(), watches)
6913 };
6914
6915 let mut first = None;
6916 let mut front = None;
6917 for viewer in 0..1000 {
6918 let session = producer.consume().excluding(Hop::random());
6919 let pending = session.request_broadcast("room/alice");
6920 if viewer == 0 {
6921 queued(&server).await.accept(&upstream);
6922 }
6923 let resolved = pending.await.expect("resolves");
6924 let mut subscription = resolved.track("video").unwrap().subscribe(None).await.unwrap();
6925 next_group(&mut subscription).await.unwrap().expect("the live group");
6926 drop(subscription);
6927 first.get_or_insert_with(counts);
6928 front = Some(resolved);
6929 }
6930 assert_eq!(first, Some((1, 1)), "one front and one watch for the first viewer");
6931 assert_eq!(counts(), (1, 1), "viewers minted fronts of their own");
6932 drop(front);
6933 }
6934
6935 #[tokio::test]
6939 async fn a_hop_in_a_chain_gets_its_own_front() {
6940 let producer = origin(1).produce();
6941 let _incumbent = producer
6942 .dynamic("room", Route::default().with_hops(hops(&[10])))
6943 .unwrap();
6944 let fronts = || producer.shared.lock().fronts.len();
6945
6946 let peer = producer.consume().excluding(origin(7));
6947 let _local = producer.consume().request_broadcast("room/alice");
6948 let _peer = peer.request_broadcast("room/alice");
6949 assert_eq!(fronts(), 1, "a hop no chain names excludes nothing");
6950
6951 let _echo = producer
6953 .dynamic("room", Route::default().with_hops(hops(&[10, 7])))
6954 .unwrap();
6955 let _filtered = peer.request_broadcast("room/alice");
6956 assert_eq!(fronts(), 2, "the peer still shares the plain front");
6957 let _viewer = producer.consume().excluding(origin(8)).request_broadcast("room/alice");
6958 assert_eq!(fronts(), 2, "a viewer left the plain front");
6959 }
6960
6961 #[tokio::test]
6965 async fn a_hairpin_through_the_plain_front_is_withdrawn() {
6966 let producer = origin(1).produce();
6967 let peer = producer.consume().excluding(origin(7));
6968 let mut view = peer.announced();
6969 let incumbent = producer
6970 .dynamic("room", Route::default().with_hops(hops(&[10])))
6971 .unwrap();
6972 view.assert_next_active("room");
6973
6974 let pending = peer.request_broadcast("room/alice");
6975 let source = broadcast::Info::new().produce();
6976 queued(&incumbent).await.accept(&source);
6977 let resolved = pending.await.expect("resolves through the incumbent");
6978
6979 let echo = producer
6981 .dynamic("room", Route::default().with_hops(hops(&[10, 7])))
6982 .unwrap();
6983 drop(incumbent);
6984 drop(source);
6985
6986 let replacement = broadcast::Info::new().produce();
6987 queued(&echo).await.accept(&replacement);
6988 resolved.assert_not_closed();
6989 view.assert_next_ended("room");
6990 }
6991
6992 #[tokio::test]
6993 async fn routes_report_where_they_entered() {
6994 let producer = origin(1).produce();
6995 let peer = producer.clone().peer();
6996 let mut announced = producer.consume().announced();
6997
6998 let _ingest = producer
6999 .dynamic("client", Route::default().with_hops(hops(&[5])).with_via(origin(5)))
7000 .unwrap();
7001 let _gateway = producer.publish("gateway", Route::default()).unwrap();
7002 let _forwarded = peer
7003 .dynamic(
7004 "forwarded",
7005 Route::default().with_hops(hops(&[5, 7])).with_via(origin(7)),
7006 )
7007 .unwrap();
7008
7009 assert_eq!(announced.assert_next_active("client").source(), Source::Local);
7010 assert_eq!(
7011 announced.assert_next_active("forwarded").source(),
7012 Source::Peer(origin(7))
7013 );
7014 assert_eq!(announced.assert_next_active("gateway").source(), Source::Local);
7015
7016 let scoped = peer.scope("room", &Patterns::from(Pattern::all())).unwrap();
7018 let _nested = scoped.dynamic("x", Route::default().with_via(origin(8))).unwrap();
7019 assert_eq!(announced.assert_next_active("room/x").source(), Source::Peer(origin(8)));
7020 }
7021
7022 #[tokio::test(start_paused = true)]
7026 async fn try_next_delivers_a_held_update() {
7027 async fn step(by: Duration) {
7029 tokio::time::advance(by).await;
7030 for _ in 0..10 {
7031 tokio::task::yield_now().await;
7032 }
7033 }
7034
7035 let producer = origin(1).produce();
7036 let peer = producer.clone().peer();
7037 let mut announced = producer.consume().announced();
7038 step(Duration::from_millis(1)).await;
7039
7040 let route = |chain: &[u64], via| Route::default().with_hops(hops(chain)).with_via(origin(via));
7041 let near = peer.dynamic("room", route(&[9, 2], 2)).unwrap();
7042 assert!(announced.try_next().unwrap().kind.is_active());
7043
7044 let _far = peer.dynamic("room", route(&[9, 3, 4], 4)).unwrap();
7045 drop(near);
7046 assert!(announced.try_next().is_none(), "the update is held");
7047 step(Duration::from_millis(1)).await;
7048 assert!(announced.try_next().is_none(), "the update is still held");
7049 step(DEFAULT_UPDATE_HOLD).await;
7050 let update = announced.try_next().expect("the hold passed");
7051 assert_eq!(update.kind, AnnounceKind::Updated);
7052 assert_eq!(update.route.hops, hops(&[9, 3, 4]));
7053 }
7054
7055 #[tokio::test(start_paused = true)]
7058 async fn a_changed_route_waits_out_the_hold() {
7059 let producer = origin(1).produce();
7060 let peer = producer.clone().peer();
7061 let mut announced = producer.consume().announced();
7062 tokio::time::sleep(Duration::from_millis(1)).await;
7064
7065 let route = |chain: &[u64], via| Route::default().with_hops(hops(chain)).with_via(origin(via));
7066 let near = peer.dynamic("room", route(&[9, 2], 2)).unwrap();
7067 announced.assert_next_active("room");
7068
7069 let far = peer.dynamic("room", route(&[9, 3, 4], 4)).unwrap();
7071 let farther = peer.dynamic("room", route(&[9, 5, 6, 7], 7)).unwrap();
7072 let start = tokio::time::Instant::now();
7073 drop(near);
7074 drop(far);
7075 let update = announced.next().await.unwrap();
7076 assert_eq!(update.kind, AnnounceKind::Updated);
7077 assert_eq!(update.route.hops, hops(&[9, 5, 6, 7]));
7078 assert_eq!(start.elapsed(), DEFAULT_UPDATE_HOLD);
7079 announced.assert_next_wait();
7080
7081 drop(farther);
7083 announced.assert_next_ended("room");
7084 }
7085
7086 #[tokio::test]
7090 async fn withdrawn_peer_hides_routes_through_it() {
7091 let producer = origin(1).produce();
7092 let peer = producer.clone().peer();
7093 let mut announced = producer.consume().announced();
7094
7095 let direct = || Route::default().with_hops(hops(&[9, 2])).with_via(origin(2));
7097 let first = peer.dynamic("room", direct()).unwrap();
7098 let relayed = peer
7099 .dynamic("room", Route::default().with_hops(hops(&[9, 2, 3])).with_via(origin(3)))
7100 .unwrap();
7101 announced.assert_next_active("room");
7102 announced.assert_next_wait();
7103
7104 first.withdrawn();
7106 announced.assert_next_ended("room");
7107 announced.assert_next_wait();
7108
7109 let second = peer.dynamic("room", direct()).unwrap();
7111 announced.assert_next_active("room");
7112 assert!(producer.shared.lock().withdrawn.is_empty());
7113
7114 second.withdrawn();
7116 drop(relayed);
7117 announced.assert_next_ended("room");
7118 assert!(producer.shared.lock().withdrawn.is_empty());
7119 }
7120
7121 #[tokio::test]
7123 async fn old_session_withdrawal_keeps_newer_route() {
7124 for restart in [false, true] {
7125 let producer = origin(1).produce();
7126 let peer = producer.clone().peer();
7127 let mut announced = producer.consume().announced();
7128 let route = Route::default().with_hops(hops(&[9, 2]));
7129 let first = peer.dynamic("room", route.clone()).unwrap();
7130 let second = peer.dynamic("room", route.clone()).unwrap();
7131 announced.assert_next_active("room");
7132 announced.assert_next_wait();
7133 let remaining = if restart {
7134 first.update(route).unwrap();
7136 second.withdrawn();
7137 first
7138 } else {
7139 first.withdrawn();
7140 second
7141 };
7142 announced.assert_next_wait();
7143 assert!(producer.shared.lock().withdrawn.is_empty());
7144 remaining.withdrawn();
7145 announced.assert_next_ended("room");
7146 assert!(producer.shared.lock().withdrawn.is_empty());
7147 }
7148 }
7149
7150 #[tokio::test]
7151 async fn withdrawn_route_no_longer_covers_requests() {
7152 let producer = origin(1).produce();
7153 let peer = producer.clone().peer();
7154 let direct = peer.dynamic("room", Route::default().with_hops(hops(&[9, 2]))).unwrap();
7155 let _relayed = peer
7156 .dynamic("room", Route::default().with_hops(hops(&[9, 2, 3])))
7157 .unwrap();
7158 let path = Path::new("room/video");
7159 let id = producer
7160 .shared
7161 .read()
7162 .routes
7163 .covering(&path)
7164 .find(|entry| entry.hops.iter().last() == Some(&origin(3)))
7165 .unwrap()
7166 .id;
7167 assert!(producer.shared.read().routes.covers(&path, id));
7168 direct.withdrawn();
7169 assert!(!producer.shared.read().routes.covers(&path, id));
7170 }
7171
7172 #[tokio::test]
7175 async fn source_change_is_an_update() {
7176 let producer = origin(1).produce();
7177 let peer = producer.clone().peer();
7178 let mut announced = producer.consume().announced();
7179
7180 let route = Route::default().with_hops(hops(&[7])).with_via(origin(7));
7181 let _forwarded = peer.dynamic("room", route.clone()).unwrap();
7182 assert_eq!(announced.assert_next_active("room").source(), Source::Peer(origin(7)));
7183
7184 let local = producer.dynamic("room", route).unwrap();
7186 let update = announced.next().now_or_never().expect("next blocked").expect("no next");
7187 assert_eq!(update.kind, AnnounceKind::Updated);
7188 assert_eq!(update.route.source(), Source::Local);
7189
7190 drop(local);
7191 assert_eq!(announced.assert_next_active("room").source(), Source::Peer(origin(7)));
7192 }
7193
7194 #[tokio::test]
7195 async fn local_view_hides_peer_routes() {
7196 let producer = origin(1).produce();
7197 let peer = producer.clone().peer();
7198 let mut local = producer.consume().local().announced();
7199
7200 let _forwarded = peer
7201 .dynamic("remote", Route::default().with_hops(hops(&[7])).with_via(origin(7)))
7202 .unwrap();
7203 local.assert_next_wait();
7204
7205 let _shadow = peer
7209 .dynamic("both", Route::default().with_hops(hops(&[7])).with_via(origin(7)))
7210 .unwrap();
7211 let ingest = producer
7212 .dynamic(
7213 "both",
7214 Route::default().with_hops(hops(&[5])).with_via(origin(5)).with_cost(9),
7215 )
7216 .unwrap();
7217 assert_eq!(local.assert_next_active("both").source(), Source::Local);
7218 drop(ingest);
7219 local.assert_next_ended("both");
7220
7221 let err = producer
7224 .consume()
7225 .local()
7226 .request_broadcast("remote/alice")
7227 .now_or_never()
7228 .expect("unroutable")
7229 .err()
7230 .unwrap();
7231 assert!(matches!(err, Error::Unroutable));
7232 assert!(
7233 producer
7234 .consume()
7235 .request_broadcast("remote/alice")
7236 .now_or_never()
7237 .is_none()
7238 );
7239 }
7240
7241 #[tokio::test]
7245 async fn handler_rejection_is_final() {
7246 let producer = origin(1).produce();
7247 let consumer = producer.consume();
7248 let server = producer.dynamic("room", Route::default()).unwrap();
7249
7250 let pending = consumer.request_broadcast("room/alice");
7251 let request = queued(&server).await;
7252 request.reject(Error::Unroutable);
7253 let err = tokio::time::timeout(Duration::from_secs(5), pending)
7254 .await
7255 .expect("the front must give up, not spin")
7256 .err()
7257 .unwrap();
7258 assert!(matches!(err, Error::Unroutable));
7259
7260 let pending = consumer.request_broadcast("room/bob");
7262 let request = queued(&server).await;
7263 assert_eq!(request.path().as_str(), "room/bob");
7264 let served = broadcast::Info::new().produce();
7265 request.accept(&served);
7266 pending.await.expect("resolves");
7267 }
7268
7269 #[tokio::test]
7272 async fn routed_broadcast_waits_out_a_rejection() {
7273 let producer = origin(1).produce();
7274 let consumer = producer.consume();
7275 let server = producer.dynamic("room", Route::default()).unwrap();
7276
7277 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
7278 assert!((&mut resolving).now_or_never().is_none());
7279 let request = queued(&server).await;
7280 request.reject(Error::Unroutable);
7281
7282 for _ in 0..20 {
7284 tokio::task::yield_now().await;
7285 }
7286 assert!((&mut resolving).now_or_never().is_none());
7287 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
7288
7289 server.update(Route::default().with_cost(2)).unwrap();
7291 assert!((&mut resolving).now_or_never().is_none());
7292 let request = queued(&server).await;
7293 let served = broadcast::Info::new().produce();
7294 request.accept(&served);
7295 resolving.await.expect("resolves");
7296 }
7297
7298 #[tokio::test]
7301 async fn routed_broadcast_reports_teardown_as_closed() {
7302 let (producer, driver) = Producer::new(Config::new(origin(1)));
7303 let consumer = producer.consume();
7304 let _server = producer.dynamic("room", Route::default()).unwrap();
7305
7306 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
7308 assert!((&mut resolving).now_or_never().is_none());
7309
7310 drop(driver);
7311
7312 let err = tokio::time::timeout(Duration::from_secs(5), resolving)
7313 .await
7314 .expect("teardown resolves the wait")
7315 .err()
7316 .unwrap();
7317 assert!(matches!(err, Error::Closed), "unexpected end: {err}");
7318 }
7319
7320 #[tokio::test]
7323 async fn routed_broadcast_wakes_for_a_local_broadcast() {
7324 let producer = origin(1).produce();
7325 let consumer = producer.consume();
7326 let server = producer.dynamic("room", Route::default()).unwrap();
7327
7328 let mut resolving = Box::pin(consumer.routed_broadcast("room/alice"));
7329 assert!((&mut resolving).now_or_never().is_none());
7330 queued(&server).await.reject(Error::Unroutable);
7331 for _ in 0..20 {
7332 tokio::task::yield_now().await;
7333 }
7334 assert!((&mut resolving).now_or_never().is_none());
7335
7336 let _local = producer.publish("room/alice", Route::default()).unwrap();
7338 let resolved = resolving.await.expect("resolves locally");
7339 assert_eq!(resolved.info().path.as_str(), "room/alice");
7340 assert!(server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending());
7341 }
7342
7343 #[tokio::test]
7346 async fn late_track_on_a_served_front_replays() {
7347 let producer = origin(1).produce();
7348 let consumer = producer.consume();
7349 let server = producer.dynamic("room", Route::default()).unwrap();
7350
7351 let source = broadcast::Info::new().produce();
7352 for name in ["a", "b"] {
7353 let track = source.create_track(name, None).unwrap();
7354 let mut group = track.append_group().unwrap();
7355 group.write_frame(crate::Timestamp::ZERO, name.as_bytes()).unwrap();
7356 group.finish().unwrap();
7357 std::mem::forget(track);
7359 }
7360
7361 let pending = consumer.request_broadcast("room/alice");
7362 queued(&server).await.accept(&source);
7363 let resolved = pending.await.expect("resolves");
7364
7365 let budget = track::Subscription::default().with_max_age(Duration::from_secs(3600));
7366 for name in ["a", "b"] {
7367 let mut subscription = resolved
7368 .track(name)
7369 .unwrap()
7370 .subscribe(budget.clone())
7371 .await
7372 .expect("subscribe");
7373 let mut group = tokio::time::timeout(Duration::from_secs(5), subscription.recv_group())
7374 .await
7375 .expect("the late track must replay, not park")
7376 .expect("recv group")
7377 .expect("track ended early");
7378 let frame = group.read_frame().await.expect("read frame").expect("frame");
7379 assert_eq!(&frame.payload[..], name.as_bytes());
7380 }
7381 }
7382
7383 #[tokio::test]
7384 async fn most_specific_prefix_shadows() {
7385 let producer = origin(1).produce();
7386 let consumer = producer.consume();
7387
7388 let broad_server = producer.dynamic("", Route::default()).unwrap();
7389 let _narrow = producer.announce(".dash", Route::default()).unwrap();
7392
7393 let err = consumer
7394 .request_broadcast(".dash/pid")
7395 .now_or_never()
7396 .expect("unroutable")
7397 .err()
7398 .unwrap();
7399 assert!(matches!(err, Error::Unroutable));
7400
7401 let _pending = consumer.request_broadcast("room/alice");
7403 let request = queued(&broad_server).await;
7404 assert_eq!(request.path().as_str(), "room/alice");
7405 }
7406
7407 #[tokio::test]
7408 async fn root_dynamic_serves_any_path() {
7409 let producer = origin(1).produce();
7410 let consumer = producer.consume();
7411 let mut announced = consumer.announced();
7412 let dynamic = producer.dynamic("", Route::default()).unwrap();
7413 announced.assert_next_active("");
7415
7416 let pending = consumer.request_broadcast("anything/at/all");
7417 let request = queued(&dynamic).await;
7418 assert_eq!(request.path().as_str(), "anything/at/all");
7419
7420 let source = broadcast::Info::new().produce();
7421 request.accept(&source);
7422 let resolved = pending.await.expect("resolves");
7423 assert_eq!(resolved.info().path.as_str(), "anything/at/all");
7424
7425 drop(dynamic);
7427 announced.assert_next_ended("");
7428 let err = consumer
7429 .request_broadcast("something/else")
7430 .now_or_never()
7431 .expect("unroutable")
7432 .err()
7433 .unwrap();
7434 assert!(matches!(err, Error::Unroutable));
7435 }
7436
7437 #[tokio::test]
7443 async fn out_of_scope_request_never_reaches_the_dynamic_handler() {
7444 let producer = origin(1).produce();
7445 let dynamic = producer.dynamic("", Route::default()).unwrap();
7446 let scoped = producer.consume().scope("", &scopes(&["tenant-a"])).unwrap();
7447
7448 for path in ["tenant-b/live", "tenant-a-other/live"] {
7451 let refused = scoped
7452 .request_broadcast(path)
7453 .now_or_never()
7454 .expect("an out-of-scope request must be refused synchronously, not queued");
7455 assert!(matches!(refused, Err(Error::Unauthorized)));
7456 assert!(
7457 dynamic.requested_broadcast().now_or_never().is_none(),
7458 "the dynamic handler was asked to create a broadcast the requester may not read"
7459 );
7460 }
7461 }
7462
7463 #[tokio::test]
7464 async fn routed_waits_for_coverage() {
7465 let producer = origin(1).produce();
7466 let consumer = producer.consume();
7467
7468 let mut fut = consumer.routed("room/alice").boxed();
7469 assert!((&mut fut).now_or_never().is_none());
7470
7471 let _a = producer.announce("room", Route::default().with_cost(3)).unwrap();
7473 let route = fut.now_or_never().expect("covered").expect("routed");
7474 assert_eq!(route.cost, Cost::new(3));
7475
7476 consumer
7478 .routed("room/alice/cam")
7479 .now_or_never()
7480 .expect("covered")
7481 .expect("routed");
7482 }
7483
7484 #[tokio::test]
7485 async fn routed_ignores_deeper_routes() {
7486 let producer = origin(1).produce();
7487 let consumer = producer.consume();
7488
7489 let _deep = producer.announce("room/alice/cam", Route::default()).unwrap();
7491 let mut fut = consumer.routed("room/alice").boxed();
7492 assert!((&mut fut).now_or_never().is_none());
7493
7494 let _exact = producer.announce("room/alice", Route::default()).unwrap();
7495 fut.now_or_never().expect("covered").expect("routed");
7496 }
7497
7498 #[tokio::test]
7499 async fn routed_accepts_a_max_depth_path() {
7500 let producer = origin(1).produce();
7501 let consumer = producer.consume();
7502 let path = (0..Path::MAX_PARTS)
7503 .map(|i| format!("s{i}"))
7504 .collect::<Vec<_>>()
7505 .join("/");
7506 assert_eq!(Path::new(&path).parts().count(), Path::MAX_PARTS);
7507
7508 assert!(consumer.allowed().matches(&path));
7509
7510 let mut fut = consumer.routed(&path).boxed();
7511 assert!((&mut fut).now_or_never().is_none());
7512
7513 let _a = producer.announce("", Route::default()).unwrap();
7515 fut.now_or_never().expect("covered").expect("routed");
7516 }
7517
7518 #[tokio::test]
7519 async fn teardown_ends_everything() {
7520 let (producer, driver) = Producer::new(Config::new(origin(1)));
7521 let consumer = producer.consume();
7522 let _announcement = producer.announce("room", Route::default()).unwrap();
7523 let mut announced = consumer.announced();
7524 announced.assert_next_active("room");
7525
7526 let _server = producer.dynamic("served", Route::default()).unwrap();
7527 let pending = consumer.request_broadcast("served/path");
7528
7529 drop(driver);
7530
7531 announced.assert_next_active("served");
7533 assert!(announced.next().now_or_never().expect("ended").is_none());
7534
7535 assert!(pending.now_or_never().expect("rejected").is_err());
7537 assert!(matches!(producer.announce("x", Route::default()), Err(Error::Closed)));
7538 assert!(matches!(producer.create_broadcast("x"), Err(Error::Closed)));
7539 let err = consumer
7540 .request_broadcast("y")
7541 .now_or_never()
7542 .expect("closed")
7543 .err()
7544 .unwrap();
7545 assert!(matches!(err, Error::Closed));
7546
7547 let mut late = consumer.announced();
7549 assert!(late.next().now_or_never().expect("ended").is_none());
7550 }
7551
7552 struct ResumeRig {
7555 producer: Producer,
7556 resolved: broadcast::Consumer,
7557 subscription: track::Subscriber,
7558 incumbent_track: track::Producer,
7561 }
7562
7563 impl ResumeRig {
7564 async fn new(first: &[u64]) -> (Self, Dynamic, broadcast::Producer) {
7567 let producer = origin(1).produce();
7568 let consumer = producer.consume();
7569
7570 let server = producer
7571 .dynamic("room", Route::default().with_hops(hops(first)))
7572 .unwrap();
7573
7574 let pending = consumer.request_broadcast("room/alice");
7575 let request = queued(&server).await;
7576 let source = broadcast::Info::new().produce();
7577 let track = source.create_track("video", None).unwrap();
7578 let mut group = track.append_group().unwrap();
7579 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
7580 group.finish().unwrap();
7581 request.accept(&source);
7582
7583 let resolved = pending.await.expect("resolves");
7584 let mut subscription = resolved
7585 .track("video")
7586 .unwrap()
7587 .subscribe(None)
7588 .await
7589 .expect("subscribe");
7590 let mut group = subscription
7591 .recv_group()
7592 .await
7593 .expect("recv group")
7594 .expect("track ended early");
7595 let frame = group.read_frame().await.expect("read frame").expect("frame");
7596 assert_eq!(&frame.payload[..], b"before");
7597
7598 (
7599 Self {
7600 producer,
7601 resolved,
7602 subscription,
7603 incumbent_track: track,
7604 },
7605 server,
7606 source,
7607 )
7608 }
7609
7610 fn standby(&self, first: &[u64]) -> Dynamic {
7613 self.producer
7614 .dynamic("room", Route::default().with_hops(hops(first)))
7615 .unwrap()
7616 }
7617 }
7618
7619 async fn assert_resumes(rig: &mut ResumeRig, server: &Dynamic) {
7624 let request = queued(server).await;
7625 let replacement = broadcast::Info::new().produce();
7626 let track = replacement.create_track("video", None).unwrap();
7627 let mut group = track.append_group().unwrap();
7630 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
7631 group.finish().unwrap();
7632 request.accept(&replacement);
7633
7634 let mut group = track.append_group().unwrap();
7635 group.write_frame(crate::Timestamp::ZERO, b"resumed".as_ref()).unwrap();
7636 group.finish().unwrap();
7637
7638 let mut group = rig
7639 .subscription
7640 .recv_group()
7641 .await
7642 .expect("subscription survives the failover")
7643 .expect("track ended early");
7644 let frame = group.read_frame().await.expect("read frame").expect("frame");
7645 assert_eq!(&frame.payload[..], b"resumed");
7646 }
7647
7648 #[tokio::test]
7651 async fn driver_resolves_with_live_consumers() {
7652 let (producer, driver) = Producer::new(Config::new(origin(1)));
7653 let consumer = producer.consume();
7654 let run = crate::time::run(driver);
7655 drop(producer);
7656 tokio::time::timeout(Duration::from_secs(5), run)
7657 .await
7658 .expect("driver must finish once the producers are gone");
7659 drop(consumer);
7660 }
7661
7662 #[tokio::test]
7663 async fn remote_source_resumes_through_same_first_hop() {
7664 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
7665 let standby_server = rig.standby(&[10, 20]);
7666
7667 drop(incumbent);
7669 drop(source);
7670
7671 assert_resumes(&mut rig, &standby_server).await;
7673 }
7674
7675 #[tokio::test]
7679 async fn incompatible_successor_is_refused() {
7680 for replacement in [
7681 track::Info::default().with_timescale(crate::Timescale::MICRO),
7682 track::Info::default().with_priority(7),
7683 track::Info::default().with_max_age(Duration::from_secs(7)),
7684 ] {
7685 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
7686 let standby_server = rig.standby(&[10, 20]);
7687 drop(incumbent);
7688 drop(source);
7689
7690 let request = queued(&standby_server).await;
7693 let successor = broadcast::Info::new().produce();
7694 let track = successor.create_track("video", replacement).unwrap();
7695 let mut group = track.append_group().unwrap();
7696 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
7697 group.finish().unwrap();
7698 request.accept(&successor);
7699
7700 assert!(
7701 matches!(rig.subscription.recv_group().await, Err(Error::Unsupported)),
7702 "the subscription must abort rather than resume onto incompatible metadata"
7703 );
7704
7705 let reopened = rig.resolved.track("video").unwrap();
7707 assert!(matches!(reopened.query().await, Err(Error::Unsupported)));
7708 assert!(matches!(reopened.subscribe(None).await, Err(Error::Unsupported)));
7709 }
7710 }
7711
7712 #[tokio::test]
7713 async fn different_first_hop_ends_the_subscription() {
7714 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
7715 let rival_server = rig.standby(&[11]);
7717
7718 drop(incumbent);
7721 drop(source);
7722 rig.incumbent_track.abort(Error::Dropped).unwrap();
7723
7724 let err = rig.subscription.recv_group().await.err().expect("subscription ends");
7726 assert!(matches!(err, Error::Dropped), "unexpected end: {err}");
7727
7728 let consumer = rig.producer.consume();
7730 let pending = consumer.request_broadcast("room/alice");
7731 let request = queued(&rival_server).await;
7732 let replacement = broadcast::Info::new().produce();
7733 request.accept(&replacement);
7734 pending.await.expect("re-request resolves through the rival");
7735 }
7736
7737 #[tokio::test]
7742 async fn a_first_hop_update_drains_the_old_publisher() {
7743 for first in [&[][..], &[0][..], &[10][..]] {
7744 let (mut rig, server, _source) = ResumeRig::new(first).await;
7745 server.update(Route::default().with_hops(hops(&[11]))).unwrap();
7746
7747 let mut group = rig.incumbent_track.append_group().unwrap();
7749 group.write_frame(crate::Timestamp::ZERO, b"draining".as_ref()).unwrap();
7750 group.finish().unwrap();
7751 let mut group = next_group(&mut rig.subscription)
7752 .await
7753 .expect("the in-flight subscription survives the update")
7754 .expect("track ended early");
7755 let frame = group.read_frame().await.expect("read frame").expect("frame");
7756 assert_eq!(&frame.payload[..], b"draining", "first hop {first:?}");
7757
7758 let consumer = rig.producer.consume();
7761 let pending = consumer.request_broadcast("room/alice");
7762 let request = queued(&server).await;
7763 let replacement = broadcast::Info::new().produce();
7764 let track = replacement
7765 .create_track("video", track::Info::default().with_priority(7))
7766 .unwrap();
7767 let mut group = track.append_group().unwrap();
7768 group.write_frame(crate::Timestamp::ZERO, b"new".as_ref()).unwrap();
7769 group.finish().unwrap();
7770 request.accept(&replacement);
7771
7772 let resolved = pending.await.expect("resolves through the new publisher");
7773 assert!(
7774 !resolved.is_clone(&rig.resolved),
7775 "first hop {first:?} joined the old front"
7776 );
7777 let mut fresh = resolved
7778 .track("video")
7779 .unwrap()
7780 .subscribe(None)
7781 .await
7782 .expect("the old publisher's track info does not apply");
7783 let mut group = next_group(&mut fresh)
7784 .await
7785 .expect("recv group")
7786 .expect("track ended early");
7787 let frame = group.read_frame().await.expect("read frame").expect("frame");
7788 assert_eq!(&frame.payload[..], b"new");
7789
7790 rig.incumbent_track.finish().unwrap();
7793 let end = next_group(&mut rig.subscription).await;
7794 assert!(
7795 !matches!(end, Ok(Some(_))),
7796 "first hop {first:?} spliced the new publisher into a live subscription"
7797 );
7798 }
7799 }
7800
7801 #[tokio::test]
7804 async fn a_first_hop_update_resumes_through_the_same_publisher() {
7805 let (mut rig, incumbent, _source) = ResumeRig::new(&[10]).await;
7806 let standby_server = rig.standby(&[10, 20]);
7807
7808 incumbent.update(Route::default().with_hops(hops(&[11]))).unwrap();
7809
7810 assert_resumes(&mut rig, &standby_server).await;
7811 assert!(
7812 incumbent.poll_requested_broadcast(&kio::Waiter::noop()).is_pending(),
7813 "the front never asks the new publisher"
7814 );
7815 }
7816
7817 #[tokio::test]
7818 async fn anonymous_routes_never_resume() {
7819 let (mut rig, incumbent, source) = ResumeRig::new(&[]).await;
7822 let _twin_server = rig.standby(&[]);
7823
7824 drop(incumbent);
7825 drop(source);
7826 rig.incumbent_track.abort(Error::Dropped).unwrap();
7827
7828 let err = rig.subscription.recv_group().await.err().expect("subscription ends");
7829 assert!(matches!(err, Error::Dropped), "unexpected end: {err}");
7830 }
7831
7832 #[tokio::test]
7839 async fn anonymous_handoff_serves_the_newcomer_immediately() {
7840 let producer = origin(1).produce();
7841
7842 let server_a = producer
7844 .dynamic("room", Route::default().with_hops(hops(&[10])))
7845 .unwrap();
7846
7847 let consumer = producer.consume().excluding(origin(30));
7850 let pending = consumer.request_broadcast("room/alice");
7851 let request = queued(&server_a).await;
7852 let source_a = broadcast::Info::new().produce();
7853 let track_a = source_a.create_track("video", None).unwrap();
7854 let mut group = track_a.append_group().unwrap();
7855 group.write_frame(crate::Timestamp::ZERO, b"from-a".as_ref()).unwrap();
7856 group.finish().unwrap();
7857 request.accept(&source_a);
7858
7859 let resolved_a = pending.await.expect("resolves");
7860 let mut sub_a = resolved_a
7861 .track("video")
7862 .unwrap()
7863 .subscribe(None)
7864 .await
7865 .expect("subscribe");
7866 let mut group = sub_a
7867 .recv_group()
7868 .await
7869 .expect("recv group")
7870 .expect("track ended early");
7871 assert_eq!(
7872 &group.read_frame().await.expect("read frame").expect("frame").payload[..],
7873 b"from-a"
7874 );
7875
7876 drop(track_a);
7879 drop(source_a);
7880 drop(server_a);
7881
7882 let err = sub_a.recv_group().await.err().expect("front closed");
7884 assert!(matches!(err, Error::Dropped), "unexpected end: {err}");
7885
7886 settle(|| consumer.get_broadcast("room/alice").is_none()).await;
7890 settle(|| {
7891 matches!(
7892 consumer.request_broadcast("room/alice").now_or_never(),
7893 Some(Err(Error::Unroutable))
7894 )
7895 })
7896 .await;
7897
7898 let server_b = producer
7900 .dynamic("room", Route::default().with_hops(hops(&[20])))
7901 .unwrap();
7902 let pending = consumer.request_broadcast("room/alice");
7903 let request = queued(&server_b).await;
7904 let source_b = broadcast::Info::new().produce();
7905 let track_b = source_b.create_track("video", None).unwrap();
7906 let mut group = track_b.append_group().unwrap();
7907 group.write_frame(crate::Timestamp::ZERO, b"from-b".as_ref()).unwrap();
7908 group.finish().unwrap();
7909 request.accept(&source_b);
7910
7911 let resolved_b = pending.await.expect("B's front is served immediately");
7912 assert!(
7913 !resolved_b.is_clone(&resolved_a),
7914 "B must not splice into A's closed front"
7915 );
7916
7917 let mut sub_b = resolved_b
7918 .track("video")
7919 .unwrap()
7920 .subscribe(None)
7921 .await
7922 .expect("subscribe");
7923 let mut group = sub_b
7924 .recv_group()
7925 .await
7926 .expect("recv group")
7927 .expect("track ended early");
7928 assert_eq!(
7929 &group.read_frame().await.expect("read frame").expect("frame").payload[..],
7930 b"from-b"
7931 );
7932 }
7933
7934 #[tokio::test]
7935 async fn reprice_is_invisible_to_the_subscription() {
7936 let (rig, incumbent, source) = ResumeRig::new(&[10]).await;
7937
7938 incumbent
7941 .update(Route::default().with_hops(hops(&[10])).with_cost(9))
7942 .unwrap();
7943
7944 let track = source.create_track("audio", None).unwrap();
7945 let mut group = track.append_group().unwrap();
7946 group.write_frame(crate::Timestamp::ZERO, b"steady".as_ref()).unwrap();
7947 group.finish().unwrap();
7948
7949 let mut audio = rig
7950 .resolved
7951 .track("audio")
7952 .unwrap()
7953 .subscribe(None)
7954 .await
7955 .expect("subscribe survives the reprice");
7956 let mut group = audio
7957 .recv_group()
7958 .await
7959 .expect("recv group")
7960 .expect("track ended early");
7961 let frame = group.read_frame().await.expect("read frame").expect("frame");
7962 assert_eq!(&frame.payload[..], b"steady");
7963 }
7964
7965 #[tokio::test]
7966 async fn drain_reprice_migrates_before_the_session_dies() {
7967 let (mut rig, incumbent, source) = ResumeRig::new(&[10]).await;
7968 let standby_server = rig.standby(&[10, 20]);
7969
7970 incumbent
7974 .update(Route::default().with_hops(hops(&[10])).with_cost(Cost::DRAIN))
7975 .unwrap();
7976
7977 assert_resumes(&mut rig, &standby_server).await;
7978
7979 drop(incumbent);
7981 drop(source);
7982 }
7983
7984 #[tokio::test]
7985 async fn local_sources_splice_newest_first() {
7986 let producer = origin(1).produce();
7987 let consumer = producer.consume();
7988
7989 let first = producer.publish("room/alice", Route::default()).unwrap();
7990 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
7991
7992 let second = producer.publish("room/alice", Route::default()).unwrap();
7994 let again = consumer.request_broadcast("room/alice").await.expect("resolves");
7995 assert!(again.is_clone(&resolved));
7996
7997 first.close();
7999 settle(|| consumer.get_broadcast("room/alice").is_some()).await;
8000 second.close();
8001 settle(|| consumer.get_broadcast("room/alice").is_none()).await;
8002
8003 let _third = producer.publish("room/alice", Route::default()).unwrap();
8005 assert!(consumer.get_broadcast("room/alice").is_some());
8006 }
8007
8008 #[tokio::test]
8016 async fn a_finished_broadcast_concludes_in_flight_subscriptions() {
8017 let producer = origin(1).produce();
8018 let consumer = producer.consume();
8019
8020 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
8021 let track = broadcast.create_track("video", None).unwrap();
8022
8023 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
8024 let mut subscription = resolved
8025 .track("video")
8026 .unwrap()
8027 .subscribe(None)
8028 .await
8029 .expect("subscribe");
8030 let mut group = track.append_group().unwrap();
8032 group.write_frame(crate::Timestamp::ZERO, b"tail".as_ref()).unwrap();
8033 group.finish().unwrap();
8034 track.finish().unwrap();
8035 drop(track);
8036 broadcast.close();
8037
8038 let mut group = next_group(&mut subscription)
8039 .await
8040 .expect("a cleanly finished track was served as an error")
8041 .expect("the track ended before its last group");
8042 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"tail");
8043 drop(group);
8044
8045 let end = next_group(&mut subscription)
8046 .await
8047 .expect("a cleanly finished track ended as an error");
8048 assert!(end.is_none(), "a group followed the final one");
8049 }
8050
8051 #[tokio::test]
8058 async fn a_retracted_route_concludes_in_flight_subscriptions() {
8059 let producer = origin(1).produce();
8060 let consumer = producer.consume();
8061 let server = producer
8062 .dynamic("room", Route::default().with_hops(hops(&[10])))
8063 .unwrap();
8064
8065 let pending = consumer.request_broadcast("room/alice");
8066 let request = queued(&server).await;
8067 let source = broadcast::Info::new().produce();
8068 let track = source.create_track("video", None).unwrap();
8069 request.accept(&source);
8070
8071 let resolved = pending.await.expect("resolves");
8072 let mut subscription = resolved
8073 .track("video")
8074 .unwrap()
8075 .subscribe(None)
8076 .await
8077 .expect("subscribe");
8078
8079 source.close();
8082 drop(server);
8083 settle(|| resolved.is_closed()).await;
8084 let mut group = track.append_group().unwrap();
8085 group.write_frame(crate::Timestamp::ZERO, b"tail".as_ref()).unwrap();
8086 group.finish().unwrap();
8087 track.finish().unwrap();
8088 drop(track);
8089
8090 let mut group = next_group(&mut subscription)
8091 .await
8092 .expect("a retracted route's track was served as an error")
8093 .expect("the track ended before its last group");
8094 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"tail");
8095 drop(group);
8096
8097 let end = next_group(&mut subscription)
8098 .await
8099 .expect("a cleanly finished track ended as an error");
8100 assert!(end.is_none(), "a group followed the final one");
8101 }
8102
8103 #[tokio::test]
8106 async fn a_closed_source_is_not_requested_again_from_its_standing_route() {
8107 let producer = origin(1).produce();
8108 let server = producer
8109 .dynamic("room", Route::default().with_hops(hops(&[10])))
8110 .unwrap();
8111 let pending = producer.consume().request_broadcast("room/alice");
8112 let source = broadcast::Info::new().produce();
8113 queued(&server).await.accept(&source);
8114 let resolved = pending.await.unwrap();
8115
8116 source.close();
8117 settle(|| resolved.is_closed()).await;
8118 assert!(
8119 server.poll_requested_broadcast(&kio::Waiter::noop()).is_pending(),
8120 "the closed source was requested again"
8121 );
8122 }
8123
8124 #[tokio::test]
8129 async fn origin_front_drops_the_source_when_unused() {
8130 let producer = origin(1).produce();
8131 let consumer = producer.consume();
8132
8133 let broadcast = producer.publish("room/alice", Route::default()).unwrap();
8134 let track = broadcast.create_track("video", None).unwrap();
8135 let mut group = track.append_group().unwrap();
8136 group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap();
8137 group.finish().unwrap();
8138
8139 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
8140 let mut subscription = resolved
8141 .track("video")
8142 .unwrap()
8143 .subscribe(None)
8144 .await
8145 .expect("subscribe");
8146 let mut group = subscription.recv_group().await.unwrap().unwrap();
8147 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
8148 drop(group);
8149 drop(subscription);
8150
8151 tokio::time::timeout(Duration::from_secs(1), track.unused())
8152 .await
8153 .expect("source unused should resolve far below TRACK_IDLE_LINGER")
8154 .expect("source closed");
8155
8156 let mut again = resolved
8159 .track("video")
8160 .unwrap()
8161 .subscribe(track::Subscription::default().with_max_age(Duration::from_secs(3600)))
8162 .await
8163 .expect("resubscribe");
8164 let mut group = tokio::time::timeout(Duration::from_secs(1), again.recv_group())
8165 .await
8166 .expect("cached group is still on the front")
8167 .expect("recv group")
8168 .expect("track ended early");
8169 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
8170
8171 tokio::time::timeout(Duration::from_secs(1), track.used())
8172 .await
8173 .expect("returning reader re-splices the source")
8174 .expect("source closed");
8175
8176 let mut group = track.append_group().unwrap();
8177 group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap();
8178 group.finish().unwrap();
8179 let mut group = tokio::time::timeout(Duration::from_secs(1), again.recv_group())
8180 .await
8181 .expect("groups past the cached edge come from the re-splice")
8182 .expect("recv group")
8183 .expect("track ended early");
8184 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"live");
8185 }
8186
8187 #[tokio::test]
8197 async fn resumed_reader_skips_warm_groups_behind_the_new_edge() {
8198 let ms = |v: u64| crate::Timestamp::from_millis(v).unwrap();
8199 let (_server, _upstream, mut dynamic, resolved) = served_front().await;
8200 let budget = track::Subscription::default().with_max_age(Duration::from_millis(100));
8201 let write = |source: &track::Producer, sequence: u64, millis: u64| {
8202 let mut group = source.create_group(sequence.into()).unwrap();
8203 group.write_frame(ms(millis), b"x".as_ref()).unwrap();
8204 group.finish().unwrap();
8205 };
8206 let drain = |subscription: &mut track::Subscriber| {
8207 let mut sequences = Vec::new();
8208 while let Poll::Ready(group) = subscription.poll_recv_group(&kio::Waiter::noop()) {
8209 sequences.push(group.unwrap().expect("track ended").sequence);
8210 }
8211 sequences
8212 };
8213
8214 let track = resolved.track("video").unwrap();
8215 let first = budget.clone();
8216 let subscribing = tokio::spawn(async move { track.subscribe(first).await });
8217 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
8218 .await
8219 .expect("the front asked the source")
8220 .expect("request");
8221 let source = request.resolving_start().accept(None);
8222 for (sequence, millis) in [(0, 0), (1, 20), (2, 40), (3, 60)] {
8223 write(&source, sequence, millis);
8224 }
8225 let mut subscription = subscribing.await.unwrap().expect("subscribe");
8226 next_group(&mut subscription).await.unwrap().expect("a cached group");
8227 drain(&mut subscription);
8228 drop(subscription);
8229
8230 tokio::time::timeout(Duration::from_secs(1), source.unused())
8231 .await
8232 .expect("parked")
8233 .expect("source open");
8234 drop(source);
8235
8236 let track = resolved.track("video").unwrap();
8237 let second = budget.clone();
8238 let subscribing = tokio::spawn(async move { track.subscribe(second).await });
8239 let request = tokio::time::timeout(Duration::from_secs(1), dynamic.requested_track())
8240 .await
8241 .expect("the front asked the source again")
8242 .expect("request");
8243 let mut source = request.resolving_start().accept(None);
8244 for (sequence, millis) in [(20, 400), (21, 420), (22, 440)] {
8245 write(&source, sequence, millis);
8246 }
8247 source.start_at(3).unwrap();
8250 let mut subscription = subscribing.await.unwrap().expect("resubscribe");
8251 settle(|| subscription.latest() == Some(22)).await;
8252
8253 assert_eq!(drain(&mut subscription), [3, 20, 21, 22]);
8256 }
8257
8258 #[tokio::test]
8263 async fn chained_front_drops_the_source_when_unused() {
8264 let leaf = origin(1).produce();
8265 let leaf_consumer = leaf.consume();
8266
8267 let broadcast = leaf.publish("room/alice", Route::default()).unwrap();
8268 let track = broadcast.create_track("video", None).unwrap();
8269 let mut group = track.append_group().unwrap();
8270 group.write_frame(crate::Timestamp::ZERO, b"cached".as_ref()).unwrap();
8271 group.finish().unwrap();
8272
8273 let leaf_front = leaf_consumer.request_broadcast("room/alice").await.expect("resolves");
8276
8277 let mid = origin(2).produce();
8278 let mid_server = mid.dynamic("room", Route::default().with_hops(hops(&[10]))).unwrap();
8279 let mid_pending = mid.consume().request_broadcast("room/alice");
8280 queued(&mid_server).await.accept(&leaf_front);
8281 let mid_resolved = mid_pending.await.expect("mid resolves");
8282
8283 let edge = origin(3).produce();
8284 let edge_server = edge.dynamic("room", Route::default().with_hops(hops(&[20]))).unwrap();
8285 let edge_pending = edge.consume().request_broadcast("room/alice");
8286 queued(&edge_server).await.accept(&mid_resolved);
8287 let edge_resolved = edge_pending.await.expect("edge resolves");
8288
8289 let mut subscription = edge_resolved
8290 .track("video")
8291 .unwrap()
8292 .subscribe(None)
8293 .await
8294 .expect("subscribe");
8295 let mut group = subscription.recv_group().await.unwrap().unwrap();
8296 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
8297 drop(group);
8298 drop(subscription);
8299
8300 tokio::time::timeout(Duration::from_secs(5), track.unused())
8301 .await
8302 .expect("chained unused should resolve far below TRACK_IDLE_LINGER")
8303 .expect("source closed");
8304
8305 let cached = edge_resolved.track("video").unwrap().cached_groups();
8306 assert_eq!(
8307 cached.iter().map(|(group, _)| group.sequence).collect::<Vec<_>>(),
8308 vec![0],
8309 "every front keeps the delivered groups after releasing its source"
8310 );
8311
8312 let mut subscription = edge_resolved
8314 .track("video")
8315 .unwrap()
8316 .subscribe(track::Subscription::default().with_max_age(Duration::from_secs(3600)))
8317 .await
8318 .expect("resubscribe");
8319 tokio::time::timeout(Duration::from_secs(5), track.used())
8320 .await
8321 .expect("resubscribe should reach the leaf")
8322 .expect("source open");
8323 let mut group = track.append_group().unwrap();
8324 group.write_frame(crate::Timestamp::ZERO, b"live".as_ref()).unwrap();
8325 group.finish().unwrap();
8326 let mut group = subscription.recv_group().await.unwrap().unwrap();
8327 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"cached");
8328 drop(group);
8329 let mut group = subscription.recv_group().await.unwrap().unwrap();
8330 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"live");
8331 drop(group);
8332 drop(subscription);
8333
8334 tokio::time::timeout(Duration::from_secs(5), track.unused())
8335 .await
8336 .expect("second chained unused should resolve far below TRACK_IDLE_LINGER")
8337 .expect("source closed");
8338
8339 let cached = edge_resolved.track("video").unwrap().cached_groups();
8340 assert_eq!(
8341 cached.iter().map(|(group, _)| group.sequence).collect::<Vec<_>>(),
8342 vec![0, 1],
8343 "repeated demand keeps every complete group while releasing its source"
8344 );
8345
8346 let fetch = edge_resolved.track("video").unwrap().fetch_group(2, None);
8347 let mut fetch = std::pin::pin!(fetch);
8348 assert!(futures::poll!(fetch.as_mut()).is_pending(), "fetch should re-splice");
8349 tokio::time::timeout(Duration::from_secs(5), track.used())
8350 .await
8351 .expect("fetch should reach the leaf")
8352 .expect("source open");
8353 let mut group = track.append_group().unwrap();
8354 group.write_frame(crate::Timestamp::ZERO, b"fetched".as_ref()).unwrap();
8355 group.finish().unwrap();
8356 let mut group = tokio::time::timeout(Duration::from_secs(5), fetch)
8357 .await
8358 .expect("re-spliced source should answer the fetch")
8359 .expect("fetch succeeds");
8360 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"fetched");
8361 }
8362
8363 #[tokio::test]
8367 async fn incompatible_local_source_keeps_the_incumbent() {
8368 let producer = origin(1).produce();
8369 let consumer = producer.consume();
8370
8371 let first = producer.publish("room/alice", Route::default()).unwrap();
8372 let track = first.create_track("video", None).unwrap();
8373 let resolved = consumer.request_broadcast("room/alice").await.expect("resolves");
8374 let mut subscription = resolved
8375 .track("video")
8376 .unwrap()
8377 .subscribe(None)
8378 .await
8379 .expect("subscribe");
8380 let mut group = track.append_group().unwrap();
8381 group.write_frame(crate::Timestamp::ZERO, b"before".as_ref()).unwrap();
8382 group.finish().unwrap();
8383 let mut group = subscription.recv_group().await.unwrap().unwrap();
8384 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"before");
8385
8386 let second = producer.publish("room/alice", Route::default()).unwrap();
8388 let _incompatible = second
8389 .create_track("video", track::Info::default().with_timescale(crate::Timescale::MICRO))
8390 .unwrap();
8391 for _ in 0..10 {
8392 tokio::task::yield_now().await;
8393 }
8394
8395 let mut group = track.append_group().unwrap();
8397 group.write_frame(crate::Timestamp::ZERO, b"still".as_ref()).unwrap();
8398 group.finish().unwrap();
8399 let mut group = subscription.recv_group().await.unwrap().unwrap();
8400 assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"still");
8401
8402 drop(track);
8404 first.close();
8405 assert!(matches!(subscription.recv_group().await, Err(Error::Unsupported)));
8406 }
8407
8408 #[tokio::test]
8409 async fn multiple_scopes_present_one_broad_prefix() {
8410 let producer = origin(1).produce();
8411 let _a = producer.announce("", Route::default()).unwrap();
8412
8413 let consumer = producer.consume().scope("", &scopes(&["alpha", "beta"])).unwrap();
8414 let mut announced = consumer.announced();
8415 announced.assert_next_active("");
8416 announced.assert_next_wait();
8417 }
8418
8419 #[test]
8420 fn scope_accepts_every_pattern_union() {
8421 let producer = origin(1).produce();
8422
8423 let root = producer.scope("", &Patterns::from(Pattern::all())).unwrap();
8425 assert_eq!(root.allowed(), Patterns::from(Pattern::all()));
8426
8427 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
8429 assert_eq!(scoped.allowed(), scopes(&["room"]));
8430
8431 let multi = producer.scope("", &scopes(&["room", "room/chat", "anon"])).unwrap();
8433 assert_eq!(multi.allowed(), scopes(&["room", "anon"]));
8434
8435 let consumer = producer.consume().scope("", &scopes(&["room"])).unwrap();
8437 assert_eq!(consumer.allowed(), scopes(&["room"]));
8438
8439 for text in ["room", "", "*room", "room/*", "*", "**/room", "room/**/chat", "*.hang"] {
8440 let union = Patterns::from(text.parse::<Pattern>().unwrap());
8441 assert_eq!(producer.scope("", &union).expect(text).allowed(), union, "{text}");
8442 assert_eq!(
8443 producer.consume().scope("", &union).expect(text).allowed(),
8444 union,
8445 "{text}"
8446 );
8447 }
8448
8449 let mixed: Patterns = ["room/**".parse().unwrap(), "other".parse().unwrap()]
8450 .into_iter()
8451 .collect();
8452 assert_eq!(producer.scope("", &mixed).unwrap().allowed(), mixed);
8453 }
8454
8455 #[test]
8456 fn route_table_prunes_to_empty() {
8457 let producer = origin(1).produce();
8458 let consumer = producer.consume();
8459
8460 let cursor = consumer
8463 .scope("", &scopes(&["room/a", "other/deep/head"]))
8464 .unwrap()
8465 .announced();
8466 let route = producer.announce("room/a/b/c", Route::default()).unwrap();
8467 {
8468 let table = producer.shared.lock();
8469 assert!(table.routes.root.find(Path::new("room/a/b/c").parts()).is_some());
8470 assert!(table.routes.root.find(Path::new("other/deep/head").parts()).is_some());
8471 assert_eq!(table.routes.root.cursors_below, 2);
8472 }
8473
8474 drop(route);
8475 drop(cursor);
8476 let table = producer.shared.lock();
8477 assert!(table.routes.root.is_empty());
8478 assert_eq!(table.routes.root.cursors_below, 0);
8479 }
8480
8481 #[test]
8485 fn a_published_broadcast_keeps_the_driver_running() {
8486 let (producer, mut driver) = Producer::new(Config::new(origin(1)));
8487 let waiter = kio::Waiter::noop();
8488 let broadcast = producer.create_broadcast("room/a").unwrap();
8489 drop(producer);
8490 assert!(
8491 driver.poll(Instant::now(), &waiter).is_ok(),
8492 "the broadcast is lifecycle work"
8493 );
8494 drop(broadcast);
8495 assert!(matches!(driver.poll(Instant::now(), &waiter), Err(Error::Closed)));
8496 }
8497
8498 #[tokio::test]
8501 async fn a_dynamic_keeps_the_driver_running() {
8502 let (producer, driver) = Producer::new(Config::new(origin(1)));
8503 let run = tokio::spawn(crate::time::run(driver));
8504 let consumer = producer.consume();
8505 let server = producer.dynamic("room", Route::default()).unwrap();
8506 drop(producer);
8507
8508 let pending = consumer.request_broadcast("room/alice");
8509 let request = queued(&server).await;
8510 let source = broadcast::Info::new().produce();
8511 request.accept(&source);
8512 let resolved = pending.await.expect("the dynamic still serves");
8513
8514 drop(resolved);
8515 drop(source);
8516 drop(server);
8517 tokio::time::timeout(Duration::from_secs(5), run)
8518 .await
8519 .expect("driver must finish once the dynamic is gone")
8520 .unwrap();
8521 drop(consumer);
8522 }
8523
8524 #[test]
8525 fn watch_wakes_only_for_covering_changes() {
8526 let producer = origin(1).produce();
8527 let waiter = kio::Waiter::noop();
8528 let watch = producer.shared.lock().watch(&producer.shared, &Path::new("room/a"));
8529 let seen = watch.seen();
8530
8531 let _other = producer.announce("other", Route::default()).unwrap();
8533 let _below = producer.announce("room/a/b", Route::default()).unwrap();
8534 assert!(watch.poll_changed(&waiter, seen).is_pending());
8535
8536 let above = producer.announce("room", Route::default()).unwrap();
8538 assert!(watch.poll_changed(&waiter, seen).is_ready());
8539 let seen = watch.seen();
8540 drop(above);
8541 assert!(watch.poll_changed(&waiter, seen).is_ready());
8542 let seen = watch.seen();
8543
8544 let _beside = producer.create_broadcast("room/b").unwrap();
8546 assert!(watch.poll_changed(&waiter, seen).is_pending());
8547 let _here = producer.create_broadcast("room/a").unwrap();
8548 assert!(watch.poll_changed(&waiter, seen).is_ready());
8549
8550 drop(watch);
8552 let table = producer.shared.lock();
8553 let node = table
8554 .routes
8555 .root
8556 .find(Path::new("room/a").parts())
8557 .expect("route below keeps the node");
8558 assert!(node.watches.is_empty());
8559 assert_eq!(table.routes.root.watches_below, 0);
8560 }
8561
8562 #[test]
8563 fn a_discarded_front_task_unregisters_its_watch() {
8564 let (producer, _driver) = Producer::new(Config {
8565 hop: origin(1),
8566 ..Default::default()
8567 });
8568 let consumer = producer.consume();
8569 let _served = producer.dynamic("room", Route::default()).unwrap();
8570 drop(producer);
8575 let _pending = consumer.request_broadcast("room/a");
8576 }
8577
8578 #[test]
8579 fn create_broadcast_refuses_a_path_no_pattern_can_spell() {
8580 let producer = origin(1).produce();
8581
8582 assert!(matches!(
8586 producer.create_broadcast("room/*"),
8587 Err(Error::InvalidPath(_))
8588 ));
8589 assert!(matches!(
8590 producer.announce("room/**", Route::default()),
8591 Err(Error::InvalidPath(_))
8592 ));
8593 }
8594
8595 #[test]
8596 fn scope_empty_union_grants_nothing() {
8597 let producer = origin(1).produce();
8598
8599 assert!(matches!(producer.scope("", &Patterns::new()), Err(Error::Unauthorized)));
8601 assert!(matches!(
8602 producer.consume().scope("", &Patterns::new()),
8603 Err(Error::Unauthorized)
8604 ));
8605 }
8606
8607 #[test]
8608 fn scope_nests_and_rebases_roots() {
8609 let producer = origin(1).produce();
8610
8611 let scoped = producer.scope("", &scopes(&["room"])).unwrap();
8613 let nested = scoped.scope("", &scopes(&["room/chat"])).unwrap();
8614 assert_eq!(nested.allowed(), scopes(&["room/chat"]));
8615
8616 assert!(matches!(
8618 scoped.scope("", &scopes(&["other"])),
8619 Err(Error::Unauthorized)
8620 ));
8621
8622 let rooted = nested.scope("room/chat", &Patterns::from(Pattern::all())).unwrap();
8624 assert_eq!(rooted.allowed(), scopes(&[""]));
8625
8626 let broadcast = nested.create_broadcast("room/chat/live").unwrap();
8628 assert!(producer.consume().get_broadcast("room/chat/live").is_some());
8629 broadcast.close();
8630 }
8631
8632 #[test]
8633 fn scope_intersects_and_rebases_arbitrary_grants() {
8634 let producer = origin(1).produce();
8635 let rooms = producer
8636 .scope("", &Patterns::from("room/*".parse::<Pattern>().unwrap()))
8637 .unwrap();
8638 let chats = rooms
8639 .scope("", &Patterns::from("*/chat".parse::<Pattern>().unwrap()))
8640 .unwrap();
8641 assert_eq!(chats.allowed(), Patterns::from("room/chat".parse::<Pattern>().unwrap()));
8642
8643 let exact = producer
8644 .scope("", &Patterns::from("room/alice".parse::<Pattern>().unwrap()))
8645 .unwrap();
8646 let rooted = exact.scope("room", &Patterns::from(Pattern::all())).unwrap();
8647 assert_eq!(rooted.allowed(), Patterns::from("alice".parse::<Pattern>().unwrap()));
8648 assert!(matches!(
8649 exact.scope("room/bob", &Patterns::from(Pattern::all())),
8650 Err(Error::Unauthorized)
8651 ));
8652
8653 let broadcast = exact.create_broadcast("room/alice").unwrap();
8654 assert!(matches!(
8655 exact.create_broadcast("room/alice/cam"),
8656 Err(Error::Unauthorized)
8657 ));
8658 assert!(producer.consume().get_broadcast("room/alice").is_some());
8659 drop(broadcast);
8660 }
8661
8662 #[tokio::test]
8663 async fn wildcard_scope_filters_announcements_and_reports_captures() {
8664 let producer = origin(1).produce();
8665 let consumer = producer
8666 .consume()
8667 .scope("", &Patterns::from("room/*/chat".parse::<Pattern>().unwrap()))
8668 .unwrap();
8669 let mut announced = consumer.announced();
8670
8671 let alice = producer.create_broadcast("room/alice/chat").unwrap();
8672 alice.announce(Route::default()).unwrap();
8673 let update = announced.try_next().expect("alice's chat");
8674 assert_eq!(update.prefix.as_str(), "room/alice/chat");
8675 assert_eq!(update.captures, Some(vec!["alice".parse::<Pattern>().unwrap()]));
8676
8677 let audio = producer.create_broadcast("room/alice/audio").unwrap();
8678 audio.announce(Route::default()).unwrap();
8679 announced.assert_next_wait();
8680
8681 let broad = producer.announce("room", Route::default()).unwrap();
8682 let update = announced.try_next().expect("overlapping broad route");
8683 assert_eq!(update.prefix.as_str(), "room");
8684 assert_eq!(update.captures, None, "an overlap does not pin the wildcard");
8685
8686 drop(broad);
8687 drop(audio);
8688 drop(alice);
8689 }
8690
8691 #[tokio::test]
8692 async fn local_broadcast_wins_announcement_ties() {
8693 let producer = origin(1).produce();
8694 let remote = producer.announce("room/alice", Route::default().with_cost(9)).unwrap();
8695 let local = producer.create_broadcast("room/alice").unwrap();
8696 local.announce(Route::default()).unwrap();
8697
8698 let mut announced = producer.consume().announced();
8699 let update = announced.try_next().expect("one winning route");
8700 assert_eq!(update.prefix.as_str(), "room/alice");
8701 assert_eq!(update.route.cost, Cost::default());
8702 announced.assert_next_wait();
8703
8704 drop(local);
8705 drop(remote);
8706 }
8707
8708 #[test]
8713 fn cost_charge_saturates() {
8714 assert_eq!(Cost { warm: 4, cold: 6 }.charged(5), Cost { warm: 9, cold: 11 });
8715 assert_eq!(Cost::new(u64::MAX).charged(10), Cost::new(MAX_COST));
8716
8717 assert_eq!(Cost::UNKNOWN.charged(3).cold, MAX_COST);
8720 }
8721
8722 fn expiring_origin(expiry: Duration) -> Producer {
8724 let pool = cache::Pool::new(cache::Config::default().with_expiry(expiry));
8725 Config {
8726 pool,
8727 ..Config::default()
8728 }
8729 .produce()
8730 }
8731
8732 #[tokio::test(start_paused = true)]
8736 async fn stalled_publisher_open_group_is_reclaimed() {
8737 let expiry = Duration::from_secs(1);
8738 let origin = expiring_origin(expiry);
8739 let broadcast = origin.create_broadcast("test").unwrap();
8740 let track = broadcast.create_track("video", None).unwrap();
8741
8742 let mut stalled = track.append_group().unwrap();
8743 stalled.write_frame(crate::Timestamp::ZERO, b"x".as_slice()).unwrap();
8744 let _successor = track.append_group().unwrap();
8748
8749 let mut reading = stalled.consume();
8750 assert!(reading.read_frame().await.unwrap().is_some());
8751
8752 crate::model::clock::advance(expiry * 2);
8754
8755 let reclaimed = tokio::time::timeout(Duration::from_secs(60), reading.read_frame()).await;
8758 assert!(
8759 matches!(reclaimed, Ok(Err(Error::Old))),
8760 "the sweep must reclaim an idle open group and surface the gap, got {reclaimed:?}"
8761 );
8762 }
8763
8764 #[tokio::test(start_paused = true)]
8767 async fn sweep_respects_a_disabled_expiry() {
8768 let origin = Config {
8769 pool: cache::Pool::unbounded(),
8770 ..Config::default()
8771 }
8772 .produce();
8773 let broadcast = origin.create_broadcast("test").unwrap();
8774 let track = broadcast.create_track("video", None).unwrap();
8775
8776 let mut stalled = track.append_group().unwrap();
8777 stalled.write_frame(crate::Timestamp::ZERO, b"x".as_slice()).unwrap();
8778 let _successor = track.append_group().unwrap();
8779
8780 let mut reading = stalled.consume();
8781 assert!(reading.read_frame().await.unwrap().is_some());
8782
8783 crate::model::clock::advance(Duration::from_secs(3600));
8784 tokio::time::advance(Duration::from_secs(3600)).await;
8785
8786 assert!(
8787 reading.read_frame().now_or_never().is_none(),
8788 "a pool without an expiry window never reclaims"
8789 );
8790 }
8791
8792 #[test]
8795 fn drain_cost_is_encodable() {
8796 use crate::coding::Encode;
8797
8798 let mut buf = Vec::new();
8799 Cost::DRAIN
8800 .encode(&mut buf, crate::lite::Version::Lite06)
8801 .expect("a draining route is still forwarded, so its cost must encode");
8802 }
8803}