o_sfu_core/engine/room/
directory.rs1use std::{
9 collections::BTreeMap,
10 sync::{Arc, Mutex},
11 time::Duration,
12};
13
14use time::{OffsetDateTime, format_description::well_known::Rfc3339};
15use tokio::time::Instant;
16
17use super::Room;
18use crate::engine::{RoomInstanceId, sync::lock_unpoisoned};
19
20const UNKNOWN_REMOTE_ADDRESS: &str = "unknown";
21
22fn rfc3339_now() -> String {
23 match OffsetDateTime::now_utc().format(&Rfc3339) {
24 Ok(timestamp) => timestamp,
25 Err(_error) => String::from("1970-01-01T00:00:00Z"),
26 }
27}
28
29#[derive(Debug, Clone)]
34pub(crate) struct RoomDirectoryEntry {
35 pub room: Arc<Room>,
36 pub lifecycle: RoomLifecycle,
37 pub create_date: String,
38 pub remote_address: String,
39}
40
41impl RoomDirectoryEntry {
42 fn new(room: Arc<Room>, remote_address: Option<&str>, reservation_ttl: Duration) -> Self {
43 Self {
44 room,
45 lifecycle: RoomLifecycle::new(reservation_ttl),
46 create_date: rfc3339_now(),
47 remote_address: remote_address.unwrap_or(UNKNOWN_REMOTE_ADDRESS).to_owned(),
48 }
49 }
50}
51
52#[derive(Debug)]
59struct RoomLifecycleState {
60 active_mutations: usize,
62 remove_when_idle: bool,
64 closing: bool,
66 expires_at: Option<Instant>,
68 reservation_ttl: Duration,
70}
71
72impl RoomLifecycleState {
73 fn new(reservation_ttl: Duration) -> Self {
74 Self {
75 active_mutations: 0,
76 remove_when_idle: false,
77 closing: false,
78 expires_at: Some(Instant::now() + reservation_ttl),
79 reservation_ttl,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
90pub(crate) struct RoomLifecycle {
91 state: Arc<Mutex<RoomLifecycleState>>,
92}
93
94impl RoomLifecycle {
95 pub(crate) fn new(reservation_ttl: Duration) -> Self {
96 Self {
97 state: Arc::new(Mutex::new(RoomLifecycleState::new(reservation_ttl))),
98 }
99 }
100
101 pub(crate) fn claim_expired_reservation(&self) -> bool {
103 let mut state = lock_unpoisoned(&self.state);
104 let is_expired = state.expires_at.is_some_and(|t| Instant::now() >= t);
105 if !state.closing && state.active_mutations == 0 && is_expired {
106 state.closing = true;
107 drop(state);
108 return true;
109 }
110 false
111 }
112
113 pub(crate) fn renew_reservation(&self) {
115 let mut state = lock_unpoisoned(&self.state);
116 if state.expires_at.is_some() {
117 state.expires_at = Some(Instant::now() + state.reservation_ttl);
118 }
119 }
120
121 #[cfg(any(test, feature = "testing-transport"))]
122 pub(crate) fn expire_reservation_now_for_test(&self) {
123 lock_unpoisoned(&self.state).expires_at = Some(Instant::now());
124 }
125
126 #[cfg(any(test, feature = "testing-transport"))]
127 #[must_use]
128 pub(crate) fn has_reservation_deadline_for_test(&self) -> bool {
129 lock_unpoisoned(&self.state).expires_at.is_some()
130 }
131
132 #[must_use]
138 pub(crate) fn begin(&self) -> Option<RoomLifecycleLease> {
139 let mut state = lock_unpoisoned(&self.state);
140 if state.closing || state.remove_when_idle {
141 return None;
142 }
143 state.active_mutations = state.active_mutations.checked_add(1)?;
144 let lease = RoomLifecycleLease {
145 state: Arc::clone(&self.state),
146 finished: false,
147 };
148 drop(state);
149 Some(lease)
150 }
151}
152
153#[derive(Debug)]
159pub(crate) struct RoomLifecycleLease {
160 state: Arc<Mutex<RoomLifecycleState>>,
162 finished: bool,
164}
165
166impl RoomLifecycleLease {
167 pub(crate) fn cancel(mut self) {
172 let _ = self.release(false, false);
173 }
174
175 #[must_use]
177 pub(crate) fn finish(mut self, remove_if_empty: bool, room_can_be_removed: bool) -> bool {
178 self.release(remove_if_empty, room_can_be_removed)
179 }
180
181 #[must_use]
182 fn release(&mut self, remove_if_empty: bool, room_can_be_removed: bool) -> bool {
183 if self.finished {
184 return false;
185 }
186 self.finished = true;
187 let mut state = lock_unpoisoned(&self.state);
188 if state.active_mutations > 0 {
189 state.active_mutations -= 1;
190 }
191 if remove_if_empty && room_can_be_removed {
192 state.remove_when_idle = true;
193 }
194 let idle_pending_removal = state.active_mutations == 0 && state.remove_when_idle;
195 let should_remove = idle_pending_removal && room_can_be_removed;
196 if should_remove {
197 state.closing = true;
198 } else if idle_pending_removal {
199 state.remove_when_idle = false;
202 }
203 drop(state);
204 should_remove
205 }
206
207 pub(crate) fn clear_expiration(&self) {
208 lock_unpoisoned(&self.state).expires_at = None;
209 }
210}
211
212impl Drop for RoomLifecycleLease {
213 fn drop(&mut self) {
214 let _ = self.release(false, false);
215 }
216}
217
218#[derive(Debug, Default)]
219pub(crate) struct RoomDirectory {
220 by_uuid: BTreeMap<String, RoomDirectoryEntry>,
221 uuid_by_instance: BTreeMap<RoomInstanceId, String>,
222 uuid_by_issuer: BTreeMap<String, String>,
223}
224
225impl RoomDirectory {
226 #[must_use]
227 pub(crate) fn get_by_uuid(&self, uuid: &str) -> Option<Arc<Room>> {
228 self.by_uuid.get(uuid).map(|entry| Arc::clone(&entry.room))
229 }
230
231 #[must_use]
232 pub(crate) fn entry(&self, uuid: &str) -> Option<RoomDirectoryEntry> {
233 self.by_uuid.get(uuid).cloned()
234 }
235
236 #[must_use]
237 pub(crate) fn entry_by_issuer(&self, issuer: &str) -> Option<RoomDirectoryEntry> {
238 let uuid = self.uuid_by_issuer.get(issuer)?;
239 self.entry(uuid)
240 }
241
242 #[must_use]
243 pub(crate) fn entry_by_instance_id(
244 &self,
245 room_instance_id: RoomInstanceId,
246 ) -> Option<RoomDirectoryEntry> {
247 let uuid = self.uuid_by_instance.get(&room_instance_id)?;
248 self.entry(uuid)
249 }
250
251 #[must_use]
252 pub(crate) fn entries(&self) -> Vec<RoomDirectoryEntry> {
253 self.by_uuid.values().cloned().collect()
254 }
255
256 #[must_use]
257 pub(crate) fn rooms(&self) -> Vec<Arc<Room>> {
258 self.by_uuid
259 .values()
260 .map(|entry| Arc::clone(&entry.room))
261 .collect()
262 }
263
264 pub(crate) fn insert(
265 &mut self,
266 room: Arc<Room>,
267 remote_address: Option<&str>,
268 reservation_ttl: Duration,
269 ) {
270 let room_id = room.uuid().to_owned();
271 self.uuid_by_issuer
272 .insert(room.issuer().to_owned(), room_id.clone());
273 self.uuid_by_instance
274 .insert(room.instance_id(), room_id.clone());
275 self.by_uuid.insert(
276 room_id,
277 RoomDirectoryEntry::new(room, remote_address, reservation_ttl),
278 );
279 }
280
281 #[must_use]
282 pub(crate) fn contains_current(&self, uuid: &str, room: &Arc<Room>) -> bool {
283 self.by_uuid
284 .get(uuid)
285 .is_some_and(|entry| Arc::ptr_eq(&entry.room, room))
286 }
287
288 pub(crate) fn remove_if_current(&mut self, uuid: &str, room: &Arc<Room>) {
289 if self.contains_current(uuid, room) {
290 self.by_uuid.remove(uuid);
291 self.uuid_by_issuer.remove(room.issuer());
292 self.uuid_by_instance.remove(&room.instance_id());
293 }
294 }
295}