use crate::identity::{ParticipantId, ProducerId};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
use zenoh::key_expr::OwnedKeyExpr;
use zenoh::sample::SampleKind;
use crate::bus::error::{BusError, KeyProblem, Result};
use crate::bus::metadata::ParticipantSourceIdentity;
use crate::bus::session::{BusHandle, BusOwner};
pub(crate) const PARTICIPANT_LIVELINESS_PREFIX: &str = "liveliness/participants";
#[derive(Clone, Debug, PartialEq, Eq)]
struct ParticipantReadyKey {
key: OwnedKeyExpr,
source: ParticipantSourceIdentity,
}
impl ParticipantReadyKey {
pub(crate) fn for_bus(bus: &BusHandle) -> Result<Self> {
let participant = bus
.participant()
.ok_or_else(|| BusError::invalid_key("", KeyProblem::Empty))?;
Self::new(bus.root(), participant.as_str(), bus.producer())
}
fn new(root: &str, participant: impl Into<String>, producer: ProducerId) -> Result<Self> {
validate_root(root)?;
let participant = participant.into();
validate_participant(&participant)?;
let participant = ParticipantId::new(participant.clone())
.map_err(|_| BusError::invalid_key(participant, KeyProblem::NotOneSegment))?;
let source = ParticipantSourceIdentity::new(participant, producer);
let raw = format!(
"{root}/{PARTICIPANT_LIVELINESS_PREFIX}/{}/{}",
source.participant, source.producer
);
let key = OwnedKeyExpr::new(raw.clone())
.map_err(|error| BusError::not_a_key_expression(&raw, error))?;
Ok(Self { key, source })
}
fn as_str(&self) -> &str {
self.key.as_str()
}
#[cfg(test)]
fn participant(&self) -> &ParticipantId {
&self.source.participant
}
#[cfg(test)]
fn producer(&self) -> ProducerId {
self.source.producer
}
fn source(&self) -> &ParticipantSourceIdentity {
&self.source
}
fn parse(root: &str, key: &str) -> Option<Self> {
let suffix = key.strip_prefix(root)?.strip_prefix('/')?;
let suffix = suffix
.strip_prefix(PARTICIPANT_LIVELINESS_PREFIX)?
.strip_prefix('/')?;
let (participant, producer) = suffix.split_once('/')?;
if producer.contains('/') {
return None;
}
Self::new(root, participant, ProducerId::parse(producer).ok()?).ok()
}
fn selector(root: &str) -> Result<OwnedKeyExpr> {
validate_root(root)?;
let selector = format!("{root}/{PARTICIPANT_LIVELINESS_PREFIX}/*/*");
OwnedKeyExpr::new(selector.clone())
.map_err(|error| BusError::not_a_key_expression(&selector, error))
}
fn participant_selector(root: &str, participant: &ParticipantId) -> Result<OwnedKeyExpr> {
validate_root(root)?;
validate_participant(participant.as_str())?;
let selector = format!("{root}/{PARTICIPANT_LIVELINESS_PREFIX}/{participant}/*");
OwnedKeyExpr::new(selector.clone())
.map_err(|error| BusError::not_a_key_expression(&selector, error))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LivelinessStatus {
Alive,
Lost,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParticipantReadyStatus {
Ready,
Lost,
}
impl From<SampleKind> for ParticipantReadyStatus {
fn from(kind: SampleKind) -> Self {
match kind {
SampleKind::Put => ParticipantReadyStatus::Ready,
SampleKind::Delete => ParticipantReadyStatus::Lost,
}
}
}
impl From<SampleKind> for LivelinessStatus {
fn from(kind: SampleKind) -> Self {
match kind {
SampleKind::Put => LivelinessStatus::Alive,
SampleKind::Delete => LivelinessStatus::Lost,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParticipantReadyEvent {
pub source: ParticipantSourceIdentity,
pub status: ParticipantReadyStatus,
}
impl ParticipantReadyEvent {
pub fn participant(&self) -> &ParticipantId {
&self.source.participant
}
pub fn producer(&self) -> ProducerId {
self.source.producer
}
}
pub struct ParticipantReadyToken {
_token: zenoh::liveliness::LivelinessToken,
source: ParticipantSourceIdentity,
}
pub struct KeyLivelinessToken {
_token: zenoh::liveliness::LivelinessToken,
relative_key: String,
}
impl KeyLivelinessToken {
pub fn relative_key(&self) -> &str {
&self.relative_key
}
}
impl ParticipantReadyToken {
pub fn source(&self) -> &ParticipantSourceIdentity {
&self.source
}
pub fn participant(&self) -> &ParticipantId {
&self.source.participant
}
pub fn producer(&self) -> ProducerId {
self.source.producer
}
}
pub struct ParticipantReadyObserver {
_subscriber: zenoh::pubsub::Subscriber<()>,
}
pub struct ParticipantReadyEvents {
receiver: Mutex<mpsc::Receiver<ParticipantReadyEvent>>,
_observer: ParticipantReadyObserver,
overflowed: Arc<AtomicBool>,
}
impl ParticipantReadyEvents {
pub fn try_recv(&self) -> Option<ParticipantReadyEvent> {
match self.receiver.lock() {
Ok(mut receiver) => receiver.try_recv().ok(),
Err(_) => {
self.overflowed.store(true, Ordering::Release);
None
}
}
}
pub fn overflowed(&self) -> bool {
self.overflowed.load(Ordering::Acquire)
}
}
pub struct KeyLivelinessObserver {
_subscriber: zenoh::pubsub::Subscriber<()>,
initial: LivelinessStatus,
}
impl KeyLivelinessObserver {
pub fn initial(&self) -> LivelinessStatus {
self.initial
}
}
impl BusOwner {
#[allow(
dead_code,
reason = "the supervisor's own presence key, declared by the one profile that compiles a supervisor"
)]
pub async fn declare_liveliness_key(&self, relative_key: &str) -> Result<KeyLivelinessToken> {
validate_relative_key(relative_key)?;
if relative_key.starts_with(PARTICIPANT_LIVELINESS_PREFIX) {
return Err(BusError::invalid_key(
relative_key,
KeyProblem::ReservedPrefix,
));
}
let bus = self.handle();
let token = bus
.session()?
.liveliness()
.declare_token(bus.full_key(relative_key))
.await
.map_err(|error| BusError::Transport(error.to_string()))?;
Ok(KeyLivelinessToken {
_token: token,
relative_key: relative_key.to_string(),
})
}
pub async fn declare_participant_ready(&self) -> Result<ParticipantReadyToken> {
let bus = self.handle();
self.declare_ready(ParticipantReadyKey::for_bus(&bus)?)
.await
}
#[allow(
dead_code,
reason = "delegated presence, declared by the one profile that compiles a simulator"
)]
pub async fn declare_participant_ready_as(
&self,
participant: &ParticipantId,
) -> Result<ParticipantReadyToken> {
let bus = self.handle();
self.declare_ready(ParticipantReadyKey::new(
bus.root(),
participant.as_str(),
bus.producer(),
)?)
.await
}
async fn declare_ready(&self, key: ParticipantReadyKey) -> Result<ParticipantReadyToken> {
let token = self
.handle()
.session()?
.liveliness()
.declare_token(key.as_str())
.await
.map_err(|error| BusError::Transport(error.to_string()))?;
Ok(ParticipantReadyToken {
_token: token,
source: key.source().clone(),
})
}
}
impl BusHandle {
pub async fn participant_ready_events(&self) -> Result<ParticipantReadyEvents> {
self.participant_ready_events_with_selector(ParticipantReadyKey::selector(self.root())?)
.await
}
pub async fn participant_ready_events_for(
&self,
participant: &ParticipantId,
) -> Result<ParticipantReadyEvents> {
self.participant_ready_events_with_selector(ParticipantReadyKey::participant_selector(
self.root(),
participant,
)?)
.await
}
pub async fn observe_participant_ready_for(
&self,
participant: &ParticipantId,
callback: impl Fn(ParticipantReadyEvent) + Send + Sync + 'static,
) -> Result<ParticipantReadyObserver> {
self.observe_participant_ready_selector(
ParticipantReadyKey::participant_selector(self.root(), participant)?,
callback,
)
.await
}
async fn participant_ready_events_with_selector(
&self,
selector: OwnedKeyExpr,
) -> Result<ParticipantReadyEvents> {
let (sender, receiver) = mpsc::channel(64);
let overflowed = Arc::new(AtomicBool::new(false));
let dropped = Arc::clone(&overflowed);
let observer = self
.observe_participant_ready_selector(selector, move |event| {
if sender.try_send(event).is_err() {
dropped.store(true, Ordering::Release);
}
})
.await?;
Ok(ParticipantReadyEvents {
receiver: Mutex::new(receiver),
_observer: observer,
overflowed,
})
}
pub async fn observe_participant_ready(
&self,
callback: impl Fn(ParticipantReadyEvent) + Send + Sync + 'static,
) -> Result<ParticipantReadyObserver> {
let root = self.root().to_string();
let selector = ParticipantReadyKey::selector(&root)?;
self.observe_participant_ready_selector(selector, callback)
.await
}
async fn observe_participant_ready_selector(
&self,
selector: OwnedKeyExpr,
callback: impl Fn(ParticipantReadyEvent) + Send + Sync + 'static,
) -> Result<ParticipantReadyObserver> {
let root = self.root().to_string();
let subscriber = self
.session()?
.liveliness()
.declare_subscriber(selector)
.history(true)
.callback(move |sample| {
if let Some(event) =
participant_event(&root, sample.key_expr().as_str(), sample.kind())
{
callback(event);
}
})
.await
.map_err(|error| BusError::Transport(error.to_string()))?;
Ok(ParticipantReadyObserver {
_subscriber: subscriber,
})
}
pub async fn observe_liveliness_key(
&self,
relative_key: &str,
callback: impl Fn(LivelinessStatus) + Send + Sync + 'static,
) -> Result<KeyLivelinessObserver> {
validate_relative_key(relative_key)?;
let raw = self.full_key(relative_key);
let key = OwnedKeyExpr::new(raw.clone())
.map_err(|error| BusError::not_a_key_expression(&raw, error))?;
let session = self.session()?;
let subscriber = session
.liveliness()
.declare_subscriber(key.clone())
.callback(move |sample| callback(LivelinessStatus::from(sample.kind())))
.await
.map_err(|error| BusError::Transport(error.to_string()))?;
let replies = session
.liveliness()
.get(key)
.await
.map_err(|error| BusError::Transport(error.to_string()))?;
let mut initial = LivelinessStatus::Lost;
while let Ok(reply) = replies.recv_async().await {
match reply.result() {
Ok(_) => initial = LivelinessStatus::Alive,
Err(error) => {
return Err(BusError::Transport(format!(
"the liveliness query for '{raw}' failed: {error:?}"
)));
}
}
}
Ok(KeyLivelinessObserver {
_subscriber: subscriber,
initial,
})
}
}
fn validate_relative_key(relative_key: &str) -> Result<()> {
validate_concrete_path(relative_key)
}
fn participant_event(root: &str, key: &str, kind: SampleKind) -> Option<ParticipantReadyEvent> {
let key = ParticipantReadyKey::parse(root, key)?;
Some(ParticipantReadyEvent {
source: key.source().clone(),
status: ParticipantReadyStatus::from(kind),
})
}
fn validate_participant(participant: &str) -> Result<()> {
if participant.is_empty() {
return Err(BusError::invalid_key(participant, KeyProblem::Empty));
}
if participant.contains('/') {
return Err(BusError::invalid_key(
participant,
KeyProblem::NotOneSegment,
));
}
if participant.contains('*') {
return Err(BusError::invalid_key(participant, KeyProblem::Wildcard));
}
Ok(())
}
fn validate_root(root: &str) -> Result<()> {
validate_concrete_path(root)
}
fn validate_concrete_path(path: &str) -> Result<()> {
if path.is_empty() || path.split('/').any(str::is_empty) {
return Err(BusError::invalid_key(path, KeyProblem::Empty));
}
if path.contains('*') {
return Err(BusError::invalid_key(path, KeyProblem::Wildcard));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const ROOT: &str = "phoxal/ffffffffffffffffffffffffffffffff";
fn producer(value: u128) -> ProducerId {
ProducerId::try_from((1_u128 << 124) | value).expect("a test producer is canonical")
}
#[test]
fn key_builder_owns_validation_and_round_trips_identity() {
let producer = producer(1);
let key = ParticipantReadyKey::new(ROOT, "drive", producer).unwrap();
assert_eq!(
key.as_str(),
format!("{ROOT}/liveliness/participants/drive/{producer}")
);
assert_eq!(
ParticipantReadyKey::parse(ROOT, key.as_str()),
Some(key.clone())
);
assert_eq!(key.participant().as_str(), "drive");
assert_eq!(key.producer(), producer);
assert!(ParticipantReadyKey::new(ROOT, "bad/id", producer).is_err());
assert!(ParticipantReadyKey::new("phoxal/*", "drive", producer).is_err());
assert!(
ParticipantReadyKey::parse(
ROOT,
&format!("{ROOT}/liveliness/participants/drive/not-a-producer")
)
.is_none()
);
}
#[test]
fn participant_event_maps_sample_kinds() {
let producer = producer(2);
let key = format!("{ROOT}/liveliness/participants/drive/{producer}");
let alive = participant_event(ROOT, &key, SampleKind::Put).unwrap();
let lost = participant_event(ROOT, &key, SampleKind::Delete).unwrap();
assert_eq!(alive.participant().as_str(), "drive");
assert_eq!(alive.producer(), producer);
assert_eq!(alive.status, ParticipantReadyStatus::Ready);
assert_eq!(lost.status, ParticipantReadyStatus::Lost);
assert!(participant_event(ROOT, "other/robot/key", SampleKind::Put).is_none());
}
#[test]
fn observer_selector_covers_exactly_the_emitted_identity_segments() {
let root = ROOT;
let selector = ParticipantReadyKey::selector(root).unwrap();
let key = ParticipantReadyKey::new(root, "drive", producer(3)).unwrap();
assert_eq!(
selector.as_str(),
format!("{ROOT}/liveliness/participants/*/*")
);
assert!(selector.includes(&key.key));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn a_stand_in_lease_is_an_ordinary_participant_ready_key() {
use crate::bus::session::BusConfig;
use crate::identity::ExecutionId;
let controller = ParticipantId::new("webots").unwrap();
let driver = ParticipantId::new("front_left_drive").unwrap();
let (owner, bus) = BusOwner::open(BusConfig::for_participant(
ExecutionId::mint(),
controller.clone(),
Vec::new(),
))
.await
.unwrap();
let stand_in = owner.declare_participant_ready_as(&driver).await.unwrap();
assert_eq!(stand_in.participant(), &driver);
assert_eq!(stand_in.producer(), bus.producer());
let expected =
ParticipantReadyKey::new(bus.root(), driver.as_str(), bus.producer()).unwrap();
assert_eq!(
expected.as_str(),
format!(
"{}/{PARTICIPANT_LIVELINESS_PREFIX}/{driver}/{}",
bus.root(),
bus.producer()
)
);
assert!(
ParticipantReadyKey::selector(bus.root())
.unwrap()
.includes(&expected.key)
);
let own = owner.declare_participant_ready().await.unwrap();
assert_eq!(own.producer(), stand_in.producer());
assert_eq!(own.participant(), &controller);
owner.close().await;
}
#[test]
fn participant_scoped_selector_excludes_unrelated_ready_churn() {
let root = ROOT;
let expected = ParticipantId::new("safety").unwrap();
let selected = ParticipantReadyKey::participant_selector(root, &expected).unwrap();
let safety = ParticipantReadyKey::new(root, "safety", producer(4)).unwrap();
let navigation = ParticipantReadyKey::new(root, "navigation", producer(5)).unwrap();
assert_eq!(
selected.as_str(),
format!("{root}/liveliness/participants/safety/*")
);
assert!(selected.includes(&safety.key));
assert!(!selected.includes(&navigation.key));
}
}