use crate::{error::DdsResult, DomainParticipant, Qos};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
pub struct ParticipantPool {
participants: Arc<Mutex<HashMap<u32, PoolEntry>>>,
}
struct PoolEntry {
participant: DomainParticipant,
last_heartbeat: Instant,
}
#[derive(Debug, Clone)]
pub struct DiscoveredTopic {
pub name: String,
pub type_name: String,
}
#[derive(Debug, Clone)]
pub struct DiscoveredParticipant {
pub domain_id: u32,
pub name: Option<String>,
}
impl ParticipantPool {
pub fn new() -> Self {
ParticipantPool {
participants: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn join_domain(&self, domain_id: u32, qos: Option<&Qos>) -> DdsResult<()> {
let participant = if let Some(q) = qos {
DomainParticipant::with_qos(domain_id, Some(q))?
} else {
DomainParticipant::new(domain_id)?
};
let mut p = self.participants.lock().unwrap();
p.insert(
domain_id,
PoolEntry {
participant,
last_heartbeat: Instant::now(),
},
);
Ok(())
}
pub fn leave_domain(&self, domain_id: u32) -> DdsResult<()> {
let mut p = self.participants.lock().unwrap();
p.remove(&domain_id);
Ok(())
}
pub fn get(&self, domain_id: u32) -> Option<DomainParticipant> {
self.participant_for_domain(domain_id).ok()
}
fn participant_for_domain(&self, domain_id: u32) -> DdsResult<DomainParticipant> {
self.participants
.lock()
.unwrap()
.get(&domain_id)
.map(|entry| entry.participant.clone())
.ok_or_else(|| crate::DdsError::BadParameter("domain not in pool".into()))
}
pub fn discover_topics(
&self,
domain_id: u32,
timeout: Duration,
) -> DdsResult<Vec<DiscoveredTopic>> {
let participant = self.participant_for_domain(domain_id)?;
let reader = participant.create_builtin_topic_reader()?;
let deadline = Instant::now() + timeout;
let mut discovered = Vec::new();
while Instant::now() < deadline {
for sample in reader.take()? {
discovered.push(DiscoveredTopic {
name: sample.topic_name(),
type_name: sample.type_name_value(),
});
}
std::thread::sleep(Duration::from_millis(50));
}
Ok(discovered)
}
pub fn discover_participants(
&self,
domain_id: u32,
timeout: Duration,
) -> DdsResult<Vec<DiscoveredParticipant>> {
let participant = self.participant_for_domain(domain_id)?;
let reader = participant.create_builtin_participant_reader()?;
let deadline = Instant::now() + timeout;
let mut discovered = Vec::new();
while Instant::now() < deadline {
for sample in reader.take()? {
discovered.push(DiscoveredParticipant {
domain_id,
name: sample.participant_name(),
});
}
std::thread::sleep(Duration::from_millis(50));
}
Ok(discovered)
}
pub fn heartbeat(&self, domain_id: u32) -> DdsResult<()> {
let mut p = self.participants.lock().unwrap();
if let Some(entry) = p.get_mut(&domain_id) {
entry.last_heartbeat = Instant::now();
Ok(())
} else {
Err(crate::DdsError::BadParameter("domain not in pool".into()))
}
}
pub fn purge_stale(&self, threshold: Duration) -> Vec<u32> {
let mut p = self.participants.lock().unwrap();
let now = Instant::now();
let stale: Vec<u32> = p
.iter()
.filter(|(_, entry)| now.duration_since(entry.last_heartbeat) > threshold)
.map(|(&id, _)| id)
.collect();
for id in &stale {
p.remove(id);
}
stale
}
pub fn domains(&self) -> Vec<u32> {
let p = self.participants.lock().unwrap();
p.keys().copied().collect()
}
}
impl Default for ParticipantPool {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::entity::DdsEntity;
#[test]
fn participant_lookup_reuses_stored_entity_and_releases_pool_lock() {
let pool = ParticipantPool::new();
pool.join_domain(172, None).unwrap();
let stored = pool
.participants
.lock()
.unwrap()
.get(&172)
.unwrap()
.participant
.entity();
let fetched = pool.participant_for_domain(172).unwrap();
assert_eq!(fetched.entity(), stored);
assert!(pool.participants.try_lock().is_ok());
}
#[test]
fn discovery_wait_does_not_block_other_pool_operations() {
let pool = Arc::new(ParticipantPool::new());
pool.join_domain(173, None).unwrap();
let discovery_pool = Arc::clone(&pool);
let discovery = std::thread::spawn(move || {
discovery_pool
.discover_topics(173, Duration::from_millis(500))
.unwrap()
});
std::thread::sleep(Duration::from_millis(100));
let started = Instant::now();
pool.heartbeat(173).unwrap();
assert!(
started.elapsed() < Duration::from_millis(100),
"heartbeat waited for the discovery loop to release the pool lock"
);
discovery.join().unwrap();
}
}