opendev_runtime/
session_status.rs1use std::collections::HashMap;
8use std::sync::Mutex;
9
10use serde::{Deserialize, Serialize};
11
12use crate::event_bus::{EventBus, RuntimeEvent, now_ms};
13
14#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
16#[serde(tag = "type")]
17pub enum SessionStatus {
18 #[default]
20 #[serde(rename = "idle")]
21 Idle,
22 #[serde(rename = "busy")]
24 Busy,
25 #[serde(rename = "retry")]
27 Retry {
28 attempt: u32,
30 message: String,
32 next_retry_ms: u64,
34 },
35}
36
37impl std::fmt::Display for SessionStatus {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Self::Idle => write!(f, "idle"),
41 Self::Busy => write!(f, "busy"),
42 Self::Retry {
43 attempt, message, ..
44 } => write!(f, "retry (attempt {attempt}: {message})"),
45 }
46 }
47}
48
49pub struct SessionStatusTracker {
54 state: Mutex<HashMap<String, SessionStatus>>,
55 event_bus: Option<EventBus>,
56}
57
58impl SessionStatusTracker {
59 pub fn new() -> Self {
61 Self {
62 state: Mutex::new(HashMap::new()),
63 event_bus: None,
64 }
65 }
66
67 pub fn with_event_bus(event_bus: EventBus) -> Self {
69 Self {
70 state: Mutex::new(HashMap::new()),
71 event_bus: Some(event_bus),
72 }
73 }
74
75 pub fn get(&self, session_id: &str) -> SessionStatus {
79 self.state
80 .lock()
81 .expect("SessionStatusTracker lock poisoned")
82 .get(session_id)
83 .cloned()
84 .unwrap_or_default()
85 }
86
87 pub fn set(&self, session_id: impl Into<String>, status: SessionStatus) {
92 let session_id = session_id.into();
93 let mut state = self
94 .state
95 .lock()
96 .expect("SessionStatusTracker lock poisoned");
97
98 match &status {
99 SessionStatus::Idle => {
100 state.remove(&session_id);
101 }
102 _ => {
103 state.insert(session_id.clone(), status.clone());
104 }
105 }
106
107 if let Some(bus) = &self.event_bus {
109 bus.publish(RuntimeEvent::SessionStatusChanged {
110 session_id,
111 status,
112 timestamp_ms: now_ms(),
113 });
114 }
115 }
116
117 pub fn set_busy(&self, session_id: impl Into<String>) {
119 self.set(session_id, SessionStatus::Busy);
120 }
121
122 pub fn set_idle(&self, session_id: impl Into<String>) {
124 self.set(session_id, SessionStatus::Idle);
125 }
126
127 pub fn set_retry(
129 &self,
130 session_id: impl Into<String>,
131 attempt: u32,
132 message: impl Into<String>,
133 next_retry_ms: u64,
134 ) {
135 self.set(
136 session_id,
137 SessionStatus::Retry {
138 attempt,
139 message: message.into(),
140 next_retry_ms,
141 },
142 );
143 }
144
145 pub fn list(&self) -> HashMap<String, SessionStatus> {
147 self.state
148 .lock()
149 .expect("SessionStatusTracker lock poisoned")
150 .clone()
151 }
152
153 pub fn active_count(&self) -> usize {
155 self.state
156 .lock()
157 .expect("SessionStatusTracker lock poisoned")
158 .len()
159 }
160
161 pub fn has_retrying(&self) -> bool {
163 self.state
164 .lock()
165 .expect("SessionStatusTracker lock poisoned")
166 .values()
167 .any(|s| matches!(s, SessionStatus::Retry { .. }))
168 }
169}
170
171impl Default for SessionStatusTracker {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177impl std::fmt::Debug for SessionStatusTracker {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 let count = self.active_count();
180 f.debug_struct("SessionStatusTracker")
181 .field("active_sessions", &count)
182 .finish()
183 }
184}
185
186#[cfg(test)]
187#[path = "session_status_tests.rs"]
188mod tests;