Add naming and navigation helpers for TUI backend sessions (#58722)

## What changed

Add `BackendSessions` helpers to name the displayed listening backend, find the first listening backend, and cycle through listening backends in registration order. Cycling wraps around, skips closed streams, and excludes the current backend. Display names use `Local` for the initial session and `Backend N` for additional sessions; a closed displayed stream has no name.

## Testing

Extend backend session tests to cover cycling with a single backend, wraparound across three backends, and skipping a closed stream.

GitOrigin-RevId: e8f3fcc38a178b6b91d385d65d899fdd042062c5
This commit is contained in:
Benjamin Carlsson
2026-11-01 15:59:49 +00:00
committed by copyberry
parent 181b4a853d
commit 54201ea65a
2 changed files with 41 additions and 1 deletions
+28
View File
@@ -118,6 +118,34 @@ impl BackendSessions {
.collect()
}
#[expect(dead_code, reason = "used by the next stack stage")]
pub(super) fn displayed_name(&self) -> Option<String> {
let slot = self.displayed.0;
self.entries[slot].listening.then(|| match slot {
0 => "Local".to_string(),
index => format!("Backend {}", index + 1),
})
}
#[expect(dead_code, reason = "used by the next stack stage")]
pub(super) fn first_listening_backend(&self) -> Option<BackendId> {
self.entries
.iter()
.find(|entry| entry.listening)
.map(|entry| entry.session.backend_id())
}
/// Cycle through listening connections in registration order, excluding the current one.
#[cfg_attr(not(test), expect(dead_code, reason = "used by the next stack stage"))]
pub(super) fn next_listening_backend(&self, current: BackendId) -> Option<BackendId> {
let index = self.slot(current)?.0;
self.entries[index + 1..]
.iter()
.chain(&self.entries[..index])
.find(|entry| entry.listening)
.map(|entry| entry.session.backend_id())
}
pub(super) fn session_at_mut(&mut self, slot: SessionSlot) -> &mut AppServerSession {
&mut self.entries[slot.0].session
}
@@ -43,7 +43,10 @@ async fn backend_sessions_parked_request_does_not_hide_disconnect() -> Result<()
)
});
let background_id = background.backend_id();
let mut backends = BackendSessions::from_initial(displayed, vec![background]);
let displayed_id = displayed.backend_id();
let mut backends = BackendSessions::new(displayed);
assert_eq!(backends.next_listening_backend(displayed_id), None);
backends.insert(background);
let initial = backends.displayed;
let thread_id = ThreadId::new();
@@ -185,6 +188,13 @@ async fn backend_sessions_poll_three_sources_and_isolate_closed_or_paused_stream
}
let mut sessions = sessions.into_iter();
let mut backends = BackendSessions::from_initial(sessions.next().unwrap(), sessions.collect());
assert_eq!(
owners
.iter()
.map(|owner| backends.next_listening_backend(*owner))
.collect::<Vec<_>>(),
[Some(owners[1]), Some(owners[2]), Some(owners[0])]
);
let mut unpaused = HashSet::new();
for _ in 0..2 {
let (owner, event) =
@@ -221,6 +231,8 @@ async fn backend_sessions_poll_three_sources_and_isolate_closed_or_paused_stream
}
}
assert!(!backends.is_listening(owners[0]));
assert_eq!(backends.next_listening_backend(owners[2]), Some(owners[1]));
assert_eq!(backends.next_listening_backend(owners[0]), Some(owners[1]));
for index in 1..3 {
assert!(backends.is_listening(owners[index]));
let _: ThreadSettingsUpdateResponse = backends