1use std::collections::{HashMap, HashSet, VecDeque};
4use std::sync::Arc;
5
6use parking_lot::Mutex;
7use tokio::sync::mpsc;
8
9use crate::SessionSlot;
10use crate::protocol::SessionUpdate;
11
12#[derive(Clone, Debug)]
21pub struct SeatUpdate {
22 pub seq: Option<u64>,
23 pub update: Arc<SessionUpdate>,
28}
29
30const SEAT_RING: usize = crate::conversation_window::CONVERSATION_CAP;
38
39const SEAT_RING_BYTES: usize = crate::conversation_window::CONVERSATION_CAP_BYTES;
49
50fn ringed(update: &SessionUpdate) -> bool {
56 !matches!(
57 update,
58 SessionUpdate::Connected { .. }
59 | SessionUpdate::SessionReplaced { .. }
60 | SessionUpdate::Releasing { .. }
61 )
62}
63
64fn frame_bytes(update: &SessionUpdate) -> usize {
74 const FRAME: usize = 256;
76 let mut count = ByteCount(0);
77 match serde_json::to_writer(&mut count, update) {
78 Ok(()) => count.0.saturating_add(FRAME),
79 Err(_) => FRAME,
82 }
83}
84
85struct ByteCount(usize);
87
88impl std::io::Write for ByteCount {
89 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
90 self.0 = self.0.saturating_add(buf.len());
91 Ok(buf.len())
92 }
93
94 fn flush(&mut self) -> std::io::Result<()> {
95 Ok(())
96 }
97}
98
99struct Kept {
103 frame: SeatUpdate,
104 bytes: usize,
105}
106
107fn remember(numbers: &mut VecDeque<u64>, seq: u64, cap: usize) {
110 numbers.push_back(seq);
111 while numbers.len() > cap {
112 numbers.pop_front();
113 }
114}
115
116#[derive(Clone, Copy, PartialEq, Eq)]
118pub(crate) enum SubscriberRole {
119 Answering,
123 Observing,
127}
128
129struct Registration {
141 tx: mpsc::UnboundedSender<SeatUpdate>,
142 role: SubscriberRole,
143}
144
145struct Shared {
146 subscribers: Vec<Registration>,
147 sequences: HashMap<SessionSlot, u64>,
154 rings: HashMap<SessionSlot, VecDeque<Kept>>,
161 ring_bytes: HashMap<SessionSlot, usize>,
164 unringed: HashMap<SessionSlot, VecDeque<u64>>,
175 sealed_with_rows: HashSet<SessionSlot>,
184 ring_cap: usize,
188 ring_cap_bytes: usize,
191}
192
193impl Default for Shared {
194 fn default() -> Self {
195 Self {
196 subscribers: Vec::new(),
197 sequences: HashMap::new(),
198 rings: HashMap::new(),
199 ring_bytes: HashMap::new(),
200 unringed: HashMap::new(),
201 sealed_with_rows: HashSet::new(),
202 ring_cap: SEAT_RING,
203 ring_cap_bytes: SEAT_RING_BYTES,
204 }
205 }
206}
207
208impl Shared {
209 fn numbered(&mut self, update: SessionUpdate, charge: usize) -> SeatUpdate {
224 let Some(slot) = update.slot().cloned() else {
225 return SeatUpdate { seq: None, update: Arc::new(update) };
226 };
227 let seq = self.next_seq(&slot);
228 let epoch_frame = !ringed(&update);
229 if matches!(
230 &update,
231 SessionUpdate::Connected { .. } | SessionUpdate::SessionReplaced { .. }
232 ) {
233 self.sequences.remove(&slot);
234 self.rings.remove(&slot);
240 self.ring_bytes.remove(&slot);
241 self.unringed.remove(&slot);
242 let carried = match &update {
249 SessionUpdate::Connected { history, .. }
250 | SessionUpdate::SessionReplaced { history, .. } => !history.is_empty(),
251 _ => false,
252 };
253 if carried {
254 self.sealed_with_rows.insert(slot.clone());
255 } else {
256 self.sealed_with_rows.remove(&slot);
257 }
258 } else if matches!(update, SessionUpdate::Releasing { .. }) {
259 self.sequences.insert(slot.clone(), seq);
270 remember(self.unringed.entry(slot.clone()).or_default(), seq, self.ring_cap);
271 } else {
272 self.sequences.insert(slot.clone(), seq);
273 }
274 let frame = SeatUpdate { seq: Some(seq), update: Arc::new(update) };
275 if !epoch_frame {
276 let ring = self.rings.entry(slot.clone()).or_default();
277 ring.push_back(Kept { frame: frame.clone(), bytes: charge });
278 let bytes = self.ring_bytes.entry(slot).or_default();
279 *bytes = bytes.saturating_add(charge);
280 while ring.len() > 1 && (ring.len() > self.ring_cap || *bytes > self.ring_cap_bytes) {
284 let Some(old) = ring.pop_front() else {
285 break;
286 };
287 *bytes = bytes.saturating_sub(old.bytes);
288 }
289 }
290 frame
291 }
292
293 fn frames_between(&self, slot: &SessionSlot, from: u64, to: u64) -> Vec<SeatUpdate> {
296 self.rings
297 .get(slot)
298 .map(|ring| {
299 ring.iter()
300 .map(|kept| &kept.frame)
301 .filter(|frame| frame.seq.is_some_and(|seq| (from..=to).contains(&seq)))
302 .cloned()
303 .collect()
304 })
305 .unwrap_or_default()
306 }
307
308 fn oldest_kept(&self, slot: &SessionSlot) -> Option<u64> {
311 self.rings.get(slot).and_then(|ring| ring.front()).and_then(|kept| kept.frame.seq)
312 }
313
314 fn unringed_between(&self, slot: &SessionSlot, from: u64, to: u64) -> u64 {
317 let Some(numbers) = self.unringed.get(slot) else {
318 return 0;
319 };
320 u64::try_from(numbers.iter().filter(|number| (from..=to).contains(number)).count())
321 .unwrap_or(u64::MAX)
322 }
323
324 fn next_seq(&self, slot: &SessionSlot) -> u64 {
330 self.sequences.get(slot).copied().unwrap_or(0) + 1
331 }
332
333 fn epoch_opened_with_rows(&self, slot: &SessionSlot) -> bool {
335 self.sealed_with_rows.contains(slot)
336 }
337}
338
339#[derive(Clone, Default)]
345pub(crate) struct UpdateFanout {
346 shared: Arc<Mutex<Shared>>,
347}
348
349impl UpdateFanout {
350 pub(crate) fn subscribe(&self, role: SubscriberRole) -> mpsc::UnboundedReceiver<SeatUpdate> {
355 let (tx, rx) = mpsc::unbounded_channel();
356 self.shared.lock().subscribers.push(Registration { tx, role });
357 rx
358 }
359
360 pub(crate) fn send(&self, update: SessionUpdate) -> bool {
370 self.deliver(update, None)
371 }
372
373 pub(crate) fn next_seq(&self, slot: &SessionSlot) -> u64 {
379 self.shared.lock().next_seq(slot)
380 }
381
382 pub(crate) fn frames_between(&self, slot: &SessionSlot, from: u64, to: u64) -> Vec<SeatUpdate> {
385 self.shared.lock().frames_between(slot, from, to)
386 }
387
388 pub(crate) fn oldest_kept(&self, slot: &SessionSlot) -> Option<u64> {
391 self.shared.lock().oldest_kept(slot)
392 }
393
394 pub(crate) fn unringed_between(&self, slot: &SessionSlot, from: u64, to: u64) -> u64 {
396 self.shared.lock().unringed_between(slot, from, to)
397 }
398
399 pub(crate) fn epoch_opened_with_rows(&self, slot: &SessionSlot) -> bool {
402 self.shared.lock().epoch_opened_with_rows(slot)
403 }
404
405 #[cfg(any(test, feature = "testing"))]
408 pub(crate) fn set_ring_cap(&self, cap: usize) {
409 self.shared.lock().ring_cap = cap;
410 }
411
412 #[cfg(test)]
415 pub(crate) fn set_ring_cap_bytes(&self, bytes: usize) {
416 self.shared.lock().ring_cap_bytes = bytes;
417 }
418
419 #[cfg(test)]
421 pub(crate) fn ring_bytes(&self, slot: &SessionSlot) -> usize {
422 self.shared.lock().ring_bytes.get(slot).copied().unwrap_or(0)
423 }
424
425 #[cfg(any(test, feature = "testing"))]
431 pub(crate) fn count(&self) -> usize {
432 self.shared.lock().subscribers.len()
433 }
434
435 #[cfg(any(test, feature = "testing"))]
439 pub(crate) fn answering_count(&self) -> usize {
440 self.shared
441 .lock()
442 .subscribers
443 .iter()
444 .filter(|held| held.role == SubscriberRole::Answering)
445 .count()
446 }
447
448 pub(crate) fn send_answering(&self, update: SessionUpdate) -> bool {
459 self.deliver(update, Some(SubscriberRole::Answering))
460 }
461
462 fn deliver(&self, update: SessionUpdate, required: Option<SubscriberRole>) -> bool {
463 let charge = if ringed(&update) { frame_bytes(&update) } else { 0 };
469 let mut shared = self.shared.lock();
470 let update = shared.numbered(update, charge);
471 let Some(tail) = shared.subscribers.pop() else {
472 return false;
473 };
474 let counts = |role: SubscriberRole| required.is_none_or(|want| want == role);
475 let mut took_it = false;
476 shared.subscribers.retain(|subscriber| {
477 let live = subscriber
478 .tx
479 .send(SeatUpdate { seq: update.seq, update: Arc::clone(&update.update) })
480 .is_ok();
481 took_it |= live && counts(subscriber.role);
482 live
483 });
484 if tail.tx.send(update).is_ok() {
485 took_it |= counts(tail.role);
486 shared.subscribers.push(tail);
487 }
488 took_it
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use forge_primitives::SessionId;
495 use forge_primitives::cloud::service_status::ServiceSeverity;
496 use forge_primitives::runtime::CurrentModel;
497
498 use super::*;
499 use crate::SessionSlot;
500 use crate::protocol::NoticeSeverity;
501
502 fn status(message: &str) -> SessionUpdate {
503 SessionUpdate::ServiceStatus {
504 severity: ServiceSeverity::Warning,
505 message: message.to_owned(),
506 }
507 }
508
509 fn seat(label: &str) -> SessionSlot {
510 SessionSlot::from_str_for_test(label)
511 }
512
513 fn a_turn_ends(key: &SessionSlot) -> SessionUpdate {
516 SessionUpdate::TurnComplete { key: key.clone(), terminal_reason: None }
517 }
518
519 fn occupant_change(key: &SessionSlot) -> SessionUpdate {
521 SessionUpdate::SessionReplaced {
522 key: key.clone(),
523 session_id: SessionId("a-new-occupant".to_owned()),
524 cwd: "/tmp".to_owned(),
525 current_model: CurrentModel::new("m", "m", "m"),
526 available_models: Vec::new(),
527 mode: None,
528 history: Vec::new(),
529 dropped: 0,
530 compaction_count: 0,
531 }
532 }
533
534 fn a_big_index(key: &SessionSlot, entries: usize) -> SessionUpdate {
537 let mut index = crate::file_index::FileIndex::default();
538 for nth in 0..entries {
539 let rel = format!("src/deeply/nested/module/number-{nth:04}/file-{nth:04}.rs");
540 index.entries.insert(
541 rel.clone(),
542 crate::file_index::FileCandidate {
543 rel_path: rel.clone(),
544 rel_path_lower: rel.to_lowercase(),
545 basename_lower: rel.to_lowercase(),
546 depth: 5,
547 },
548 );
549 }
550 SessionUpdate::FileIndexChanged { key: key.clone(), index: Arc::new(index) }
551 }
552
553 fn frame(rx: &mut mpsc::UnboundedReceiver<SeatUpdate>, who: &str) -> SeatUpdate {
555 rx.try_recv().unwrap_or_else(|err| {
556 panic!("expected an update on the {who} subscriber's stream: {err}")
557 })
558 }
559
560 fn next_seq(rx: &mut mpsc::UnboundedReceiver<SeatUpdate>, who: &str) -> Option<u64> {
562 frame(rx, who).seq
563 }
564
565 fn said(frame: SeatUpdate) -> String {
566 match std::sync::Arc::unwrap_or_clone(frame.update) {
567 SessionUpdate::ServiceStatus { message, .. } => message,
568 other => panic!("expected the ServiceStatus envelope this test sends, got {other:?}"),
569 }
570 }
571
572 fn next(rx: &mut mpsc::UnboundedReceiver<SeatUpdate>, who: &str) -> String {
573 said(frame(rx, who))
574 }
575
576 #[test]
579 fn each_subscriber_receives_every_update() {
580 let fanout = UpdateFanout::default();
581 let mut first = fanout.subscribe(SubscriberRole::Answering);
582 let mut second = fanout.subscribe(SubscriberRole::Answering);
583
584 assert!(fanout.send(status("one")), "a subscribed fan-out delivers");
585
586 assert_eq!(next(&mut first, "first"), "one", "the first subscriber sees the update");
587 assert_eq!(
588 next(&mut second, "second"),
589 "one",
590 "the second subscriber sees the same update"
591 );
592 }
593
594 #[test]
600 fn a_subscriber_that_drops_leaves_the_fan_out() {
601 let fanout = UpdateFanout::default();
602 let dropped = fanout.subscribe(SubscriberRole::Answering);
603 let mut kept = fanout.subscribe(SubscriberRole::Answering);
604 drop(dropped);
605
606 assert!(fanout.send(status("one")), "the live subscriber still receives");
607
608 assert_eq!(
609 fanout.shared.lock().subscribers.len(),
610 1,
611 "the dropped subscriber is gone from the registry",
612 );
613 assert_eq!(next(&mut kept, "kept"), "one", "the live subscriber still receives");
614 }
615
616 #[test]
619 fn a_late_subscriber_sees_only_what_follows_it() {
620 let fanout = UpdateFanout::default();
621 let mut first = fanout.subscribe(SubscriberRole::Answering);
622 assert!(fanout.send(status("before")), "the first subscriber receives");
623
624 let mut late = fanout.subscribe(SubscriberRole::Answering);
625 assert!(fanout.send(status("after")), "both subscribers receive");
626
627 assert_eq!(
628 next(&mut first, "first"),
629 "before",
630 "the first subscriber keeps its earlier update"
631 );
632 assert_eq!(
633 next(&mut first, "first"),
634 "after",
635 "the first subscriber also sees the later one"
636 );
637 assert_eq!(next(&mut late, "late"), "after", "the late subscriber sees only the later one");
638 assert!(late.try_recv().is_err(), "the late subscriber carries nothing from before it");
639 }
640
641 #[test]
646 fn a_delivery_shares_one_update_with_the_ring_and_every_subscriber() {
647 let fanout = UpdateFanout::default();
648 let mut first = fanout.subscribe(SubscriberRole::Answering);
649 let mut second = fanout.subscribe(SubscriberRole::Answering);
650 let seat = seat("lead");
651
652 assert!(fanout.send(a_turn_ends(&seat)), "both subscribers receive it");
653 let one = first.try_recv().expect("the first subscriber's frame");
654 let two = second.try_recv().expect("the second subscriber's frame");
655 assert!(
656 Arc::ptr_eq(&one.update, &two.update),
657 "the two subscribers were handed one payload, not two",
658 );
659 let ringed = fanout.frames_between(&seat, 1, 1);
660 assert_eq!(ringed.len(), 1, "the ring kept the frame");
661 assert!(
662 Arc::ptr_eq(&one.update, &ringed[0].update),
663 "and the ring holds the same payload rather than a second copy",
664 );
665 }
666
667 #[test]
671 fn an_observer_takes_the_update_without_answering_it() {
672 let fanout = UpdateFanout::default();
673 let mut observer = fanout.subscribe(SubscriberRole::Observing);
674
675 assert!(!fanout.send_answering(status("one")), "an observer does not answer");
676 assert_eq!(next(&mut observer, "observer"), "one", "the observer still sees the update");
677
678 let mut answering = fanout.subscribe(SubscriberRole::Answering);
679 assert!(fanout.send_answering(status("two")), "an answering subscriber answers");
680 assert_eq!(next(&mut answering, "answering"), "two", "it sees the update too");
681 }
682
683 #[test]
686 fn send_without_a_subscriber_reports_nothing_delivered() {
687 let fanout = UpdateFanout::default();
688
689 assert!(!fanout.send(status("one")), "an unsubscribed fan-out delivers to nobody");
690 }
691
692 #[test]
694 fn a_seats_frames_carry_their_own_numbers() {
695 let fanout = UpdateFanout::default();
696 let mut rx = fanout.subscribe(SubscriberRole::Answering);
697 let seat = seat("lead");
698
699 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
700 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
701
702 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the seat's first frame is number 1");
703 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "and its next is the successor");
704 }
705
706 #[test]
712 fn an_occupant_change_restarts_the_seats_numbers() {
713 let fanout = UpdateFanout::default();
714 let mut rx = fanout.subscribe(SubscriberRole::Answering);
715 let seat = seat("lead");
716
717 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
718 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
719 assert!(fanout.send(occupant_change(&seat)), "a subscribed fan-out delivers");
720
721 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the seat's first frame is number 1");
722 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "and its next is the successor");
723 assert_eq!(
724 next_seq(&mut rx, "seat"),
725 Some(3),
726 "the occupant frame is numbered on the counter it ends",
727 );
728
729 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
730 assert_eq!(
731 next_seq(&mut rx, "seat"),
732 Some(1),
733 "and the frame after it opens the new epoch at 1",
734 );
735 }
736
737 #[test]
741 fn a_fresh_connect_opens_its_epoch_at_one() {
742 let fanout = UpdateFanout::default();
743 let mut rx = fanout.subscribe(SubscriberRole::Answering);
744 let seat = seat("lead");
745
746 assert!(
747 fanout.send(SessionUpdate::Connected {
748 key: seat.clone(),
749 session_id: SessionId("the-first-occupant".to_owned()),
750 cwd: "/tmp".to_owned(),
751 current_model: CurrentModel::new("m", "m", "m"),
752 available_models: Vec::new(),
753 mode: None,
754 history: Vec::new(),
755 dropped: 0,
756 compaction_count: 0,
757 }),
758 "a subscribed fan-out delivers",
759 );
760
761 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the connect is the seat's first frame");
762
763 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
764 assert_eq!(
765 next_seq(&mut rx, "seat"),
766 Some(1),
767 "and the epoch it opens starts at 1, so the client wiping on it expects 1",
768 );
769 }
770
771 #[test]
774 fn a_frame_for_no_seat_rides_unnumbered() {
775 let fanout = UpdateFanout::default();
776 let mut rx = fanout.subscribe(SubscriberRole::Answering);
777 let seat = seat("lead");
778
779 assert!(fanout.send(status("boot")), "a subscribed fan-out delivers");
780 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
781
782 assert_eq!(
783 next_seq(&mut rx, "app-level"),
784 None,
785 "a workspace-level frame carries no number",
786 );
787 assert_eq!(
788 next_seq(&mut rx, "seat"),
789 Some(1),
790 "and it does not advance any seat's counter",
791 );
792 }
793
794 #[test]
800 fn two_seats_keep_independent_counters() {
801 let fanout = UpdateFanout::default();
802 let mut rx = fanout.subscribe(SubscriberRole::Answering);
803 let lead = seat("lead");
804 let worker = seat("worker");
805
806 assert!(fanout.send(a_turn_ends(&lead)), "a subscribed fan-out delivers");
807 assert!(fanout.send(a_turn_ends(&worker)), "a subscribed fan-out delivers");
808 assert!(fanout.send(a_turn_ends(&lead)), "a subscribed fan-out delivers");
809 assert!(fanout.send(a_turn_ends(&worker)), "a subscribed fan-out delivers");
810
811 assert_eq!(next_seq(&mut rx, "lead"), Some(1), "the lead's first frame is number 1");
812 assert_eq!(
813 next_seq(&mut rx, "worker"),
814 Some(1),
815 "the worker's own run opens at 1 rather than continuing the lead's",
816 );
817 assert_eq!(next_seq(&mut rx, "lead"), Some(2), "the lead's next is the successor");
818 assert_eq!(next_seq(&mut rx, "worker"), Some(2), "and so is the worker's");
819 }
820
821 #[test]
828 fn an_occupant_change_leaves_the_ring_on_the_new_epoch_alone() {
829 let fanout = UpdateFanout::default();
830 let mut rx = fanout.subscribe(SubscriberRole::Answering);
831 let seat = seat("lead");
832
833 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
834 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
835 assert!(fanout.send(occupant_change(&seat)), "a subscribed fan-out delivers");
836 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
837 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
838
839 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the old run's first frame");
840 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "and its second");
841 assert_eq!(
842 next_seq(&mut rx, "seat"),
843 Some(3),
844 "the occupant frame is numbered on the counter it ends",
845 );
846 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "and the new epoch opens at 1");
847 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "counting on");
848
849 assert_eq!(
850 fanout.oldest_kept(&seat),
851 Some(1),
852 "the ring's front is the new epoch's first frame, not the frame that ended the old run",
853 );
854 assert_eq!(
855 fanout.frames_between(&seat, 1, 3).iter().map(|frame| frame.seq).collect::<Vec<_>>(),
856 vec![Some(1), Some(2)],
857 "and a range reaches only the new count's frames",
858 );
859 }
860
861 #[test]
867 fn a_release_advances_the_counter_it_is_numbered_on() {
868 let fanout = UpdateFanout::default();
869 let mut rx = fanout.subscribe(SubscriberRole::Answering);
870 let seat = seat("lead");
871
872 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
873 assert!(
874 fanout.send(SessionUpdate::Releasing { key: seat.clone() }),
875 "a subscribed fan-out delivers"
876 );
877 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
878
879 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the first frame");
880 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "the release");
881 assert_eq!(
882 next_seq(&mut rx, "seat"),
883 Some(3),
884 "and the frame after it, rather than a number minted twice",
885 );
886 }
887
888 #[test]
895 fn the_release_is_the_number_the_ring_cannot_serve() {
896 let fanout = UpdateFanout::default();
897 let mut rx = fanout.subscribe(SubscriberRole::Answering);
898 let seat = seat("lead");
899
900 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
901 assert!(
902 fanout.send(SessionUpdate::Releasing { key: seat.clone() }),
903 "a subscribed fan-out delivers"
904 );
905 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the row");
906 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "and the release after it");
907
908 assert_eq!(
909 fanout.unringed_between(&seat, 1, 1),
910 0,
911 "a row the window holds is not counted among the numbers it cannot serve",
912 );
913 assert_eq!(
914 fanout.unringed_between(&seat, 1, 2),
915 1,
916 "and the release beside it is, because no window can hold it",
917 );
918
919 assert!(fanout.send(occupant_change(&seat)), "a subscribed fan-out delivers");
920 for _ in 0..3 {
921 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
922 }
923 assert_eq!(
924 fanout.unringed_between(&seat, 1, 3),
925 0,
926 "the new epoch mints from 1 again, so the old release's number is forgotten",
927 );
928 }
929
930 #[test]
936 fn a_release_keeps_the_frames_before_it_reachable() {
937 let fanout = UpdateFanout::default();
938 let mut rx = fanout.subscribe(SubscriberRole::Answering);
939 let seat = seat("lead");
940
941 assert!(fanout.send(a_turn_ends(&seat)), "a subscribed fan-out delivers");
942 assert!(
943 fanout.send(SessionUpdate::Releasing { key: seat.clone() }),
944 "a subscribed fan-out delivers"
945 );
946 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the row");
947 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "and the release after it");
948
949 assert_eq!(
950 fanout.frames_between(&seat, 1, 1).iter().map(|frame| frame.seq).collect::<Vec<_>>(),
951 vec![Some(1)],
952 "the frame before the release is still there for a copy that is behind it",
953 );
954 assert_eq!(
955 fanout.unringed_between(&seat, 1, 2),
956 1,
957 "and the release itself is still a number no window holds",
958 );
959 }
960
961 #[test]
968 fn a_released_seat_keeps_only_what_its_byte_ceiling_holds() {
969 let fanout = UpdateFanout::default();
970 let mut rx = fanout.subscribe(SubscriberRole::Answering);
971 let seat = seat("lead");
972 fanout.set_ring_cap_bytes(10_000);
975
976 assert!(fanout.send(a_big_index(&seat, 20)), "a subscribed fan-out delivers");
977 assert!(
978 fanout.send(SessionUpdate::Releasing { key: seat.clone() }),
979 "a subscribed fan-out delivers"
980 );
981 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the frame before the release");
982 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "and the release");
983
984 for _ in 0..3 {
985 assert!(fanout.send(a_big_index(&seat, 20)), "a subscribed fan-out delivers");
986 }
987
988 assert!(
989 fanout.ring_bytes(&seat) <= 10_000,
990 "the ring answers its byte ceiling: {} bytes",
991 fanout.ring_bytes(&seat),
992 );
993 assert!(
994 fanout.frames_between(&seat, 1, 1).is_empty(),
995 "and the frame before the release is shed once the ring outgrows it",
996 );
997 assert!(
998 !fanout.frames_between(&seat, 3, 5).is_empty(),
999 "while the newest frames are what a repair is answered from",
1000 );
1001 }
1002
1003 #[test]
1008 fn a_frame_over_the_ceiling_survives_alone() {
1009 let fanout = UpdateFanout::default();
1010 let mut rx = fanout.subscribe(SubscriberRole::Answering);
1011 let seat = seat("lead");
1012 fanout.set_ring_cap_bytes(10_000);
1013
1014 assert!(fanout.send(a_big_index(&seat, 60)), "a subscribed fan-out delivers");
1016 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the frame");
1017 assert_eq!(
1018 fanout.frames_between(&seat, 1, 1).len(),
1019 1,
1020 "the newest frame survives even alone over the ceiling",
1021 );
1022 assert!(
1023 fanout.ring_bytes(&seat) > 10_000,
1024 "and the ring states its true weight: {} bytes",
1025 fanout.ring_bytes(&seat),
1026 );
1027 }
1028
1029 #[test]
1034 fn a_call_output_and_a_card_tail_are_charged_by_their_payload() {
1035 use forge_primitives::runtime::{SubagentCall, SubagentCallStatus, SubagentCard};
1036
1037 let fanout = UpdateFanout::default();
1038 let mut rx = fanout.subscribe(SubscriberRole::Answering);
1039 let seat = seat("lead");
1040 let line = "x".repeat(4_000);
1041
1042 assert!(
1043 fanout.send(SessionUpdate::CallOutput {
1044 key: seat.clone(),
1045 call_id: "tu-1".to_owned(),
1046 output: forge_primitives::CallOutput::Lines(vec![
1047 line.clone(),
1048 line.clone(),
1049 line.clone(),
1050 ]),
1051 }),
1052 "a subscribed fan-out delivers",
1053 );
1054 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the call output");
1055 let output_bytes = fanout.ring_bytes(&seat);
1056 assert!(
1057 output_bytes > 12_000,
1058 "three 4 KB lines are charged against the ceiling: {output_bytes} bytes",
1059 );
1060
1061 let card = SubagentCard {
1062 name: "an instance".to_owned(),
1063 dispatch_id: "tu-2".to_owned(),
1064 agent_type: None,
1065 running: true,
1066 failed: false,
1067 backgrounded: false,
1068 ended_at_ms: None,
1069 calls: 3,
1070 tail: (0..3)
1071 .map(|_| SubagentCall {
1072 name: "Bash".to_owned(),
1073 title: line.clone(),
1074 status: SubagentCallStatus::Completed,
1075 })
1076 .collect(),
1077 usage: None,
1078 };
1079 assert!(
1080 fanout
1081 .send(SessionUpdate::SubagentCardsChanged { key: seat.clone(), cards: vec![card] }),
1082 "a subscribed fan-out delivers",
1083 );
1084 assert_eq!(next_seq(&mut rx, "seat"), Some(2), "and the cards after it");
1085 let both = fanout.ring_bytes(&seat);
1086 assert!(
1087 both > output_bytes + 12_000,
1088 "the card tail's calls are charged too: {both} bytes over the {output_bytes} before \
1089 it",
1090 );
1091 }
1092
1093 #[test]
1099 fn a_frame_is_charged_by_its_serialised_size() {
1100 let fanout = UpdateFanout::default();
1101 let mut rx = fanout.subscribe(SubscriberRole::Answering);
1102 let seat = seat("lead");
1103
1104 assert!(
1105 fanout.send(SessionUpdate::Notice {
1106 key: seat.clone(),
1107 severity: NoticeSeverity::Info,
1108 text: "x".repeat(10_000),
1109 }),
1110 "a subscribed fan-out delivers",
1111 );
1112 assert_eq!(next_seq(&mut rx, "seat"), Some(1), "the notice");
1113 let charged = fanout.ring_bytes(&seat);
1114 assert!(
1115 charged >= 10_000,
1116 "a 10 KB notice is charged its own bytes rather than a frame's weight: {charged}",
1117 );
1118 }
1119
1120 #[test]
1123 fn a_frame_that_reached_nobody_still_advances_its_seats_number() {
1124 let fanout = UpdateFanout::default();
1125 let seat = seat("lead");
1126 assert!(!fanout.send(a_turn_ends(&seat)), "nothing has attached to take it");
1127 assert!(!fanout.send(a_turn_ends(&seat)), "nothing has attached to take it");
1128
1129 let mut first = fanout.subscribe(SubscriberRole::Answering);
1130 assert!(fanout.send(a_turn_ends(&seat)), "the subscriber is attached");
1131
1132 assert_eq!(
1133 next_seq(&mut first, "first"),
1134 Some(3),
1135 "the delivered frame continues the count rather than reusing a number",
1136 );
1137 }
1138}