From 2cc7f12a01e15517e6b8c358d2e63056d526c7ab Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 5 Nov 2024 15:38:05 +0100 Subject: [PATCH 01/88] Expose and use ke macro --- commons/zenoh-macros/src/lib.rs | 2 +- zenoh/src/api/admin.rs | 28 ++++++++++++++-------------- zenoh/src/lib.rs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/commons/zenoh-macros/src/lib.rs b/commons/zenoh-macros/src/lib.rs index 2047c424cf..cc6f4a2295 100644 --- a/commons/zenoh-macros/src/lib.rs +++ b/commons/zenoh-macros/src/lib.rs @@ -515,7 +515,7 @@ pub fn ke(tokens: TokenStream) -> TokenStream { let value: LitStr = syn::parse(tokens).unwrap(); let ke = value.value(); match zenoh_keyexpr::keyexpr::new(&ke) { - Ok(_) => quote!(unsafe {::zenoh::key_expr::keyexpr::from_str_unchecked(#ke)}).into(), + Ok(_) => quote!(unsafe { zenoh::key_expr::keyexpr::from_str_unchecked(#ke)}).into(), Err(e) => panic!("{}", e), } } diff --git a/zenoh/src/api/admin.rs b/zenoh/src/api/admin.rs index b0d0bed0f7..74960e68f7 100644 --- a/zenoh/src/api/admin.rs +++ b/zenoh/src/api/admin.rs @@ -19,6 +19,7 @@ use std::{ use zenoh_core::{Result as ZResult, Wait}; use zenoh_keyexpr::keyexpr; +use zenoh_macros::ke; #[cfg(feature = "unstable")] use zenoh_protocol::core::Reliability; use zenoh_protocol::{core::WireExpr, network::NetworkMessage}; @@ -26,6 +27,7 @@ use zenoh_transport::{ TransportEventHandler, TransportMulticastEventHandler, TransportPeer, TransportPeerEventHandler, }; +use crate as zenoh; use crate::{ api::{ encoding::Encoding, @@ -38,17 +40,15 @@ use crate::{ handlers::Callback, }; -lazy_static::lazy_static!( - static ref KE_STARSTAR: &'static keyexpr = unsafe { keyexpr::from_str_unchecked("**") }; - static ref KE_PREFIX: &'static keyexpr = unsafe { keyexpr::from_str_unchecked("@") }; - static ref KE_SESSION: &'static keyexpr = unsafe { keyexpr::from_str_unchecked("session") }; - static ref KE_TRANSPORT_UNICAST: &'static keyexpr = unsafe { keyexpr::from_str_unchecked("transport/unicast") }; - static ref KE_LINK: &'static keyexpr = unsafe { keyexpr::from_str_unchecked("link") }; -); +static KE_STARSTAR: &keyexpr = ke!("**"); +static KE_PREFIX: &keyexpr = ke!("@"); +static KE_SESSION: &keyexpr = ke!("session"); +static KE_TRANSPORT_UNICAST: &keyexpr = ke!("transport/unicast"); +static KE_LINK: &keyexpr = ke!("link"); pub(crate) fn init(session: WeakSession) { if let Ok(own_zid) = keyexpr::new(&session.zid().to_string()) { - let admin_key = KeyExpr::from(*KE_PREFIX / own_zid / *KE_SESSION / *KE_STARSTAR) + let admin_key = KeyExpr::from(KE_PREFIX / own_zid / KE_SESSION / KE_STARSTAR) .to_wire(&session) .to_owned(); @@ -68,7 +68,7 @@ pub(crate) fn on_admin_query(session: &WeakSession, query: Query) { fn reply_peer(own_zid: &keyexpr, query: &Query, peer: TransportPeer) { let zid = peer.zid.to_string(); if let Ok(zid) = keyexpr::new(&zid) { - let key_expr = *KE_PREFIX / own_zid / *KE_SESSION / *KE_TRANSPORT_UNICAST / zid; + let key_expr = KE_PREFIX / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid; if query.key_expr().intersects(&key_expr) { match serde_json::to_vec(&peer) { Ok(bytes) => { @@ -82,12 +82,12 @@ pub(crate) fn on_admin_query(session: &WeakSession, query: Query) { let mut s = DefaultHasher::new(); link.hash(&mut s); if let Ok(lid) = keyexpr::new(&s.finish().to_string()) { - let key_expr = *KE_PREFIX + let key_expr = KE_PREFIX / own_zid - / *KE_SESSION - / *KE_TRANSPORT_UNICAST + / KE_SESSION + / KE_TRANSPORT_UNICAST / zid - / *KE_LINK + / KE_LINK / lid; if query.key_expr().intersects(&key_expr) { match serde_json::to_vec(&link) { @@ -156,7 +156,7 @@ impl TransportMulticastEventHandler for Handler { if let Ok(own_zid) = keyexpr::new(&self.session.zid().to_string()) { if let Ok(zid) = keyexpr::new(&peer.zid.to_string()) { let expr = WireExpr::from( - &(*KE_PREFIX / own_zid / *KE_SESSION / *KE_TRANSPORT_UNICAST / zid), + &(KE_PREFIX / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid), ) .to_owned(); let info = DataInfo { diff --git a/zenoh/src/lib.rs b/zenoh/src/lib.rs index 701e13fc94..298ce6f1f6 100644 --- a/zenoh/src/lib.rs +++ b/zenoh/src/lib.rs @@ -172,7 +172,7 @@ pub mod key_expr { #[zenoh_macros::unstable] pub mod format { pub use zenoh_keyexpr::format::*; - pub use zenoh_macros::{kedefine, keformat, kewrite}; + pub use zenoh_macros::{ke, kedefine, keformat, kewrite}; pub mod macro_support { pub use zenoh_keyexpr::format::macro_support::*; } From 163d3d50ba38ec29167e8de3617e907aad7cdf43 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 8 Nov 2024 16:21:57 +0100 Subject: [PATCH 02/88] Fix SourceInfo publication --- zenoh/src/api/session.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/zenoh/src/api/session.rs b/zenoh/src/api/session.rs index c5ba24b98f..362fb8ce2f 100644 --- a/zenoh/src/api/session.rs +++ b/zenoh/src/api/session.rs @@ -1968,7 +1968,7 @@ impl SessionInner { timestamp, encoding: encoding.clone().into(), #[cfg(feature = "unstable")] - ext_sinfo: source_info.into(), + ext_sinfo: source_info.clone().into(), #[cfg(not(feature = "unstable"))] ext_sinfo: None, #[cfg(feature = "shared-memory")] @@ -1980,7 +1980,7 @@ impl SessionInner { SampleKind::Delete => PushBody::Del(Del { timestamp, #[cfg(feature = "unstable")] - ext_sinfo: source_info.into(), + ext_sinfo: source_info.clone().into(), #[cfg(not(feature = "unstable"))] ext_sinfo: None, ext_attachment: attachment.clone().map(|a| a.into()), @@ -1999,7 +1999,13 @@ impl SessionInner { kind, encoding: Some(encoding), timestamp, + #[cfg(feature = "unstable")] + source_id: source_info.source_id, + #[cfg(not(feature = "unstable"))] source_id: None, + #[cfg(feature = "unstable")] + source_sn: source_info.source_sn, + #[cfg(not(feature = "unstable"))] source_sn: None, qos: QoS::from(push::ext::QoSType::new( priority.into(), From 43a5d3ce19b84f6fb672702587bd194ef3f65eea Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 8 Nov 2024 16:23:16 +0100 Subject: [PATCH 03/88] Add AdvancedPublisher AdvancedSubscriber and AdvancedSubscriber --- Cargo.lock | 1 + commons/zenoh-config/src/wrappers.rs | 10 + zenoh-ext/Cargo.toml | 1 + zenoh-ext/src/advanced_cache.rs | 327 +++++++++++++ zenoh-ext/src/advanced_publisher.rs | 392 ++++++++++++++++ zenoh-ext/src/advanced_subscriber.rs | 676 +++++++++++++++++++++++++++ zenoh-ext/src/lib.rs | 14 +- zenoh-ext/src/publisher_ext.rs | 46 ++ zenoh-ext/src/session_ext.rs | 20 + zenoh-ext/src/subscriber_ext.rs | 51 +- zenoh/src/api/builders/publisher.rs | 8 + 11 files changed, 1544 insertions(+), 2 deletions(-) create mode 100644 zenoh-ext/src/advanced_cache.rs create mode 100644 zenoh-ext/src/advanced_publisher.rs create mode 100644 zenoh-ext/src/advanced_subscriber.rs create mode 100644 zenoh-ext/src/publisher_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 3fec7718ce..7beb1c9a7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5593,6 +5593,7 @@ dependencies = [ name = "zenoh-ext" version = "1.0.0-dev" dependencies = [ + "async-trait", "bincode", "flume", "futures", diff --git a/commons/zenoh-config/src/wrappers.rs b/commons/zenoh-config/src/wrappers.rs index 31afe358e7..759507c770 100644 --- a/commons/zenoh-config/src/wrappers.rs +++ b/commons/zenoh-config/src/wrappers.rs @@ -152,6 +152,16 @@ pub struct EntityGlobalId(EntityGlobalIdProto); pub type EntityId = u32; impl EntityGlobalId { + /// Creates a new EntityGlobalId. + #[zenoh_macros::internal] + pub fn new(zid: ZenohId, eid: EntityId) -> Self { + EntityGlobalIdProto { + zid: zid.into(), + eid, + } + .into() + } + /// Returns the [`ZenohId`], i.e. the Zenoh session, this ID is associated to. pub fn zid(&self) -> ZenohId { self.0.zid.into() diff --git a/zenoh-ext/Cargo.toml b/zenoh-ext/Cargo.toml index 4de255d0d6..0b693fa820 100644 --- a/zenoh-ext/Cargo.toml +++ b/zenoh-ext/Cargo.toml @@ -38,6 +38,7 @@ tokio = { workspace = true, features = [ "macros", "io-std", ] } +async-trait = { workspace = true } bincode = { workspace = true } zenoh-util = { workspace = true } flume = { workspace = true } diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs new file mode 100644 index 0000000000..b96b7fe4af --- /dev/null +++ b/zenoh-ext/src/advanced_cache.rs @@ -0,0 +1,327 @@ +// +// Copyright (c) 2022 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use std::{ + borrow::Borrow, + collections::{HashMap, VecDeque}, + future::{IntoFuture, Ready}, +}; + +use flume::{bounded, Sender}; +use futures::{select, FutureExt}; +use tokio::task; +use zenoh::{ + handlers::FifoChannelHandler, + internal::{bail, ResolveFuture}, + key_expr::{ + format::{ke, kedefine}, + keyexpr, KeyExpr, OwnedKeyExpr, + }, + liveliness::LivelinessToken, + pubsub::Subscriber, + query::{Query, Queryable, ZenohParameters}, + sample::{Locality, Sample}, + Resolvable, Resolve, Result as ZResult, Session, Wait, +}; + +#[zenoh_macros::unstable] +pub(crate) static KE_STAR: &keyexpr = ke!("*"); +#[zenoh_macros::unstable] +pub(crate) static KE_PREFIX: &keyexpr = ke!("@cache"); +#[zenoh_macros::unstable] +kedefine!( + pub(crate) ke_liveliness: "@cache/${zid:*}/${eid:*}/${remaining:**}", +); + +/// The builder of AdvancedCache, allowing to configure it. +pub struct AdvancedCacheBuilder<'a, 'b, 'c> { + session: &'a Session, + pub_key_expr: ZResult>, + queryable_prefix: Option>>, + subscriber_origin: Locality, + queryable_origin: Locality, + history: usize, + liveliness: bool, + resources_limit: Option, +} + +impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { + pub(crate) fn new( + session: &'a Session, + pub_key_expr: ZResult>, + ) -> AdvancedCacheBuilder<'a, 'b, 'c> { + AdvancedCacheBuilder { + session, + pub_key_expr, + queryable_prefix: Some(Ok((KE_PREFIX / KE_STAR / KE_STAR).into())), + subscriber_origin: Locality::default(), + queryable_origin: Locality::default(), + history: 1024, + liveliness: false, + resources_limit: None, + } + } + + /// Change the prefix used for queryable. + pub fn queryable_prefix(mut self, queryable_prefix: TryIntoKeyExpr) -> Self + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + self.queryable_prefix = Some(queryable_prefix.try_into().map_err(Into::into)); + self + } + + /// Restrict the matching publications that will be cached by this [`AdvancedCache`] + /// to the ones that have the given [`Locality`](crate::prelude::Locality). + #[inline] + pub fn subscriber_allowed_origin(mut self, origin: Locality) -> Self { + self.subscriber_origin = origin; + self + } + + /// Restrict the matching queries that will be receive by this [`AdvancedCache`]'s queryable + /// to the ones that have the given [`Locality`](crate::prelude::Locality). + #[inline] + pub fn queryable_allowed_origin(mut self, origin: Locality) -> Self { + self.queryable_origin = origin; + self + } + + /// Change the history size for each resource. + pub fn history(mut self, history: usize) -> Self { + self.history = history; + self + } + + /// Change the limit number of cached resources. + pub fn resources_limit(mut self, limit: usize) -> Self { + self.resources_limit = Some(limit); + self + } + + pub fn liveliness(mut self, enabled: bool) -> Self { + self.liveliness = enabled; + self + } +} + +impl Resolvable for AdvancedCacheBuilder<'_, '_, '_> { + type To = ZResult; +} + +impl Wait for AdvancedCacheBuilder<'_, '_, '_> { + fn wait(self) -> ::To { + AdvancedCache::new(self) + } +} + +impl<'a> IntoFuture for AdvancedCacheBuilder<'a, '_, '_> { + type Output = ::To; + type IntoFuture = Ready<::To>; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} + +fn decode_range(range: &str) -> (Option, Option) { + let mut split = range.split(".."); + let start = split.next().and_then(|s| s.parse::().ok()); + let end = split.next().map(|s| s.parse::().ok()).unwrap_or(start); + (start, end) +} + +fn sample_in_range(sample: &Sample, start: Option, end: Option) -> bool { + if start.is_none() && end.is_none() { + true + } else if let Some(source_sn) = sample.source_info().source_sn() { + match (start, end) { + (Some(start), Some(end)) => source_sn >= start && source_sn <= end, + (Some(start), None) => source_sn >= start, + (None, Some(end)) => source_sn <= end, + (None, None) => true, + } + } else { + false + } +} + +pub struct AdvancedCache { + _sub: Subscriber>, + _queryable: Queryable>, + _token: Option, + _stoptx: Sender, +} + +impl AdvancedCache { + fn new(conf: AdvancedCacheBuilder<'_, '_, '_>) -> ZResult { + let key_expr = conf.pub_key_expr?; + // the queryable_prefix (optional), and the key_expr for AdvancedCache's queryable ("[]/") + let (queryable_prefix, queryable_key_expr): (Option, KeyExpr) = + match conf.queryable_prefix { + None => (None, key_expr.clone()), + Some(Ok(ke)) => { + let queryable_key_expr = (&ke) / &key_expr; + (Some(ke.into()), queryable_key_expr) + } + Some(Err(e)) => bail!("Invalid key expression for queryable_prefix: {}", e), + }; + tracing::debug!( + "Create AdvancedCache on {} with history={} resource_limit={:?}", + &key_expr, + conf.history, + conf.resources_limit + ); + + // declare the local subscriber that will store the local publications + let sub = conf + .session + .declare_subscriber(&key_expr) + .allowed_origin(conf.subscriber_origin) + .wait()?; + + // declare the queryable that will answer to queries on cache + let queryable = conf + .session + .declare_queryable(&queryable_key_expr) + .allowed_origin(conf.queryable_origin) + .wait()?; + + // take local ownership of stuff to be moved into task + let sub_recv = sub.handler().clone(); + let quer_recv = queryable.handler().clone(); + let pub_key_expr = key_expr.into_owned(); + let resources_limit = conf.resources_limit; + let history = conf.history; + + let (stoptx, stoprx) = bounded::(1); + task::spawn(async move { + let mut cache: HashMap> = + HashMap::with_capacity(resources_limit.unwrap_or(32)); + let limit = resources_limit.unwrap_or(usize::MAX); + + loop { + select!( + // on publication received by the local subscriber, store it + sample = sub_recv.recv_async() => { + if let Ok(sample) = sample { + let queryable_key_expr: KeyExpr<'_> = if let Some(prefix) = &queryable_prefix { + prefix.join(&sample.key_expr()).unwrap().into() + } else { + sample.key_expr().clone() + }; + + if let Some(queue) = cache.get_mut(queryable_key_expr.as_keyexpr()) { + if queue.len() >= history { + queue.pop_front(); + } + queue.push_back(sample); + } else if cache.len() >= limit { + tracing::error!("AdvancedCache on {}: resource_limit exceeded - can't cache publication for a new resource", + pub_key_expr); + } else { + let mut queue: VecDeque = VecDeque::new(); + queue.push_back(sample); + cache.insert(queryable_key_expr.into(), queue); + } + } + }, + + // on query, reply with cache content + query = quer_recv.recv_async() => { + if let Ok(query) = query { + let (start, end) = query.parameters().get("_sn").map(decode_range).unwrap_or((None, None)); + if !query.selector().key_expr().as_str().contains('*') { + if let Some(queue) = cache.get(query.selector().key_expr().as_keyexpr()) { + for sample in queue { + if sample_in_range(sample, start, end) { + if let (Some(Ok(time_range)), Some(timestamp)) = (query.parameters().time_range(), sample.timestamp()) { + if !time_range.contains(timestamp.get_time().to_system_time()){ + continue; + } + } + if let Err(e) = query.reply_sample(sample.clone()).await { + tracing::warn!("Error replying to query: {}", e); + } + } + } + } + } else { + for (key_expr, queue) in cache.iter() { + if query.selector().key_expr().intersects(key_expr.borrow()) { + for sample in queue { + if sample_in_range(sample, start, end) { + if let (Some(Ok(time_range)), Some(timestamp)) = (query.parameters().time_range(), sample.timestamp()) { + if !time_range.contains(timestamp.get_time().to_system_time()){ + continue; + } + } + if let Err(e) = query.reply_sample(sample.clone()).await { + tracing::warn!("Error replying to query: {}", e); + } + } + } + } + } + } + } + }, + + // When stoptx is dropped, stop the task + _ = stoprx.recv_async().fuse() => { + return + } + ); + } + }); + + let token = if conf.liveliness { + Some( + conf.session + .liveliness() + .declare_token(queryable_key_expr) + .wait()?, + ) + } else { + None + }; + + Ok(AdvancedCache { + _sub: sub, + _queryable: queryable, + _token: token, + _stoptx: stoptx, + }) + } + + /// Close this AdvancedCache + #[inline] + pub fn close(self) -> impl Resolve> { + ResolveFuture::new(async move { + let AdvancedCache { + _queryable, + _sub, + _token, + _stoptx, + } = self; + _sub.undeclare().await?; + if let Some(token) = _token { + token.undeclare().await?; + } + _queryable.undeclare().await?; + drop(_stoptx); + Ok(()) + }) + } +} diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs new file mode 100644 index 0000000000..1e6bd77d3f --- /dev/null +++ b/zenoh-ext/src/advanced_publisher.rs @@ -0,0 +1,392 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use std::{ + future::{IntoFuture, Ready}, + sync::atomic::{AtomicU32, Ordering}, +}; + +use zenoh::{ + bytes::{Encoding, ZBytes}, + internal::bail, + key_expr::KeyExpr, + liveliness::LivelinessToken, + pubsub::{Publisher, PublisherDeleteBuilder, PublisherPutBuilder}, + qos::{CongestionControl, Priority}, + sample::{Locality, SourceInfo}, + session::EntityGlobalId, + Resolvable, Resolve, Result as ZResult, Session, Wait, +}; + +use crate::{ + advanced_cache::{AdvancedCache, KE_PREFIX}, + SessionExt, +}; + +pub enum Sequencing { + None, + Timestamp, + SequenceNumber, +} + +/// The builder of PublicationCache, allowing to configure it. +#[must_use = "Resolvables do nothing unless you resolve them using the `res` method from either `SyncResolve` or `AsyncResolve`"] +pub struct AdvancedPublisherBuilder<'a, 'b> { + session: &'a Session, + pub_key_expr: ZResult>, + sequencing: Sequencing, + liveliness: bool, + cache: bool, + history: usize, + resources_limit: Option, +} + +impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { + pub(crate) fn new( + session: &'a Session, + pub_key_expr: ZResult>, + ) -> AdvancedPublisherBuilder<'a, 'b> { + AdvancedPublisherBuilder { + session, + pub_key_expr, + sequencing: Sequencing::None, + liveliness: false, + cache: false, + history: 1, + resources_limit: None, + } + } + + /// Allow matching Subscribers to detect lost samples and ask for retransimission. + /// + /// Retransmission can only be achieved if history is enabled. + pub fn retransmission(mut self) -> Self { + self.cache = true; + self.sequencing = Sequencing::SequenceNumber; + self + } + + /// Change the history size for each resource. + pub fn history(mut self, history: usize) -> Self { + self.cache = true; + self.history = history; + self + } + + /// Change the limit number of cached resources. + pub fn resources_limit(mut self, limit: usize) -> Self { + self.resources_limit = Some(limit); + self + } + + /// Allow this publisher to be detected by subscribers. + /// + /// This allows Subscribers to retrieve the local history. + pub fn late_joiner(mut self) -> Self { + self.liveliness = true; + self + } +} + +impl<'a> Resolvable for AdvancedPublisherBuilder<'a, '_> { + type To = ZResult>; +} + +impl Wait for AdvancedPublisherBuilder<'_, '_> { + fn wait(self) -> ::To { + AdvancedPublisher::new(self) + } +} + +impl<'a> IntoFuture for AdvancedPublisherBuilder<'a, '_> { + type Output = ::To; + type IntoFuture = Ready<::To>; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} + +pub struct AdvancedPublisher<'a> { + publisher: Publisher<'a>, + seqnum: Option, + _cache: Option, + _token: Option, +} + +impl<'a> AdvancedPublisher<'a> { + fn new(conf: AdvancedPublisherBuilder<'a, '_>) -> ZResult { + let key_expr = conf.pub_key_expr?; + + let publisher = conf + .session + .declare_publisher(key_expr.clone().into_owned()) + .wait()?; + let id = publisher.id(); + let prefix = KE_PREFIX + / &id.zid().into_keyexpr() + / &KeyExpr::try_from(id.eid().to_string()).unwrap(); + + let seqnum = match conf.sequencing { + Sequencing::SequenceNumber => Some(AtomicU32::new(0)), + Sequencing::Timestamp => { + if conf.session.hlc().is_none() { + bail!( + "Cannot create AdvancedPublisher {} with Sequencing::Timestamp: \ + the 'timestamping' setting must be enabled in the Zenoh configuration.", + key_expr, + ) + } + None + } + _ => None, + }; + + let cache = if conf.cache { + let mut builder = conf + .session + .declare_advanced_cache(key_expr.clone().into_owned()) + .subscriber_allowed_origin(Locality::SessionLocal) + .history(conf.history) + .queryable_prefix(&prefix); + if let Some(resources_limit) = conf.resources_limit { + builder = builder.resources_limit(resources_limit); + } + Some(builder.wait()?) + } else { + None + }; + + let token = if conf.liveliness { + Some( + conf.session + .liveliness() + .declare_token(prefix / &key_expr) + .wait()?, + ) + } else { + None + }; + + Ok(AdvancedPublisher { + publisher, + seqnum, + _cache: cache, + _token: token, + }) + } + + /// Returns the [`EntityGlobalId`] of this Publisher. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh::prelude::*; + /// + /// let session = zenoh::open(zenoh::config::peer()).await.unwrap(); + /// let publisher = session.declare_publisher("key/expression") + /// .await + /// .unwrap(); + /// let publisher_id = publisher.id(); + /// # } + /// ``` + pub fn id(&self) -> EntityGlobalId { + self.publisher.id() + } + + #[inline] + pub fn key_expr(&self) -> &KeyExpr<'a> { + self.publisher.key_expr() + } + + /// Get the [`Encoding`] used when publishing data. + #[inline] + pub fn encoding(&self) -> &Encoding { + self.publisher.encoding() + } + + /// Get the `congestion_control` applied when routing the data. + #[inline] + pub fn congestion_control(&self) -> CongestionControl { + self.publisher.congestion_control() + } + + /// Get the priority of the written data. + #[inline] + pub fn priority(&self) -> Priority { + self.publisher.priority() + } + + /// Consumes the given `Publisher`, returning a thread-safe reference-counting + /// pointer to it (`Arc`). This is equivalent to `Arc::new(Publisher)`. + /// + /// This is useful to share ownership of the `Publisher` between several threads + /// and tasks. It also allows to create [`MatchingListener`] with static + /// lifetime that can be moved to several threads and tasks. + /// + /// Note: the given zenoh `Publisher` will be undeclared when the last reference to + /// it is dropped. + /// + /// # Examples + /// ```no_run + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh::prelude::*; + /// + /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); + /// let publisher = session.declare_publisher("key/expression").await.unwrap().into_arc(); + /// let matching_listener = publisher.matching_listener().await.unwrap(); + /// + /// tokio::task::spawn(async move { + /// while let Ok(matching_status) = matching_listener.recv_async().await { + /// if matching_status.matching_subscribers() { + /// println!("Publisher has matching subscribers."); + /// } else { + /// println!("Publisher has NO MORE matching subscribers."); + /// } + /// } + /// }).await; + /// # } + /// ``` + #[zenoh_macros::unstable] + pub fn into_arc(self) -> std::sync::Arc { + std::sync::Arc::new(self) + } + + /// Put data. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh::prelude::*; + /// + /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); + /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// publisher.put("value").await.unwrap(); + /// # } + /// ``` + #[inline] + pub fn put(&self, payload: IntoZBytes) -> PublisherPutBuilder<'_> + where + IntoZBytes: Into, + { + println!("here"); + let mut put = self.publisher.put(payload); + if let Some(seqnum) = &self.seqnum { + println!("there"); + put = put.source_info(SourceInfo::new( + Some(self.publisher.id()), + Some(seqnum.fetch_add(1, Ordering::Relaxed)), + )); + } + put + } + + /// Delete data. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh::prelude::*; + /// + /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); + /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// publisher.delete().await.unwrap(); + /// # } + /// ``` + pub fn delete(&self) -> PublisherDeleteBuilder<'_> { + let mut delete = self.publisher.delete(); + if let Some(seqnum) = &self.seqnum { + delete = delete.source_info(SourceInfo::new( + Some(self.publisher.id()), + Some(seqnum.fetch_add(1, Ordering::Relaxed)), + )); + } + delete + } + + /// Return the [`MatchingStatus`] of the publisher. + /// + /// [`MatchingStatus::matching_subscribers`] will return true if there exist Subscribers + /// matching the Publisher's key expression and false otherwise. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh::prelude::*; + /// + /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); + /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// let matching_subscribers: bool = publisher + /// .matching_status() + /// .await + /// .unwrap() + /// .matching_subscribers(); + /// # } + /// ``` + #[zenoh_macros::unstable] + pub fn matching_status(&self) -> impl Resolve> + '_ { + self.publisher.matching_status() + } + + /// Return a [`MatchingListener`] for this Publisher. + /// + /// The [`MatchingListener`] that will send a notification each time the [`MatchingStatus`] of + /// the Publisher changes. + /// + /// # Examples + /// ```no_run + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh::prelude::*; + /// + /// let session = zenoh::open(zenoh::config::peer()).await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// let matching_listener = publisher.matching_listener().await.unwrap(); + /// while let Ok(matching_status) = matching_listener.recv_async().await { + /// if matching_status.matching_subscribers() { + /// println!("Publisher has matching subscribers."); + /// } else { + /// println!("Publisher has NO MORE matching subscribers."); + /// } + /// } + /// # } + /// ``` + #[zenoh_macros::unstable] + pub fn matching_listener( + &self, + ) -> zenoh::pubsub::MatchingListenerBuilder<'_, '_, zenoh::handlers::DefaultHandler> { + self.publisher.matching_listener() + } + + /// Undeclares the [`Publisher`], informing the network that it needn't optimize publications for its key expression anymore. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh::prelude::*; + /// + /// let session = zenoh::open(zenoh::config::peer()).await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// publisher.undeclare().await.unwrap(); + /// # } + /// ``` + pub fn undeclare(self) -> impl Resolve> + 'a { + self.publisher.undeclare() + } +} diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs new file mode 100644 index 0000000000..92084181cd --- /dev/null +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -0,0 +1,676 @@ +// +// Copyright (c) 2022 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use std::{future::IntoFuture, str::FromStr}; + +use zenoh::{ + config::ZenohId, + handlers::{Callback, IntoHandler}, + key_expr::KeyExpr, + query::{ConsolidationMode, Selector}, + sample::{Locality, Sample}, + session::{EntityGlobalId, EntityId}, + Resolvable, Resolve, Session, Wait, +}; +use zenoh_util::{Timed, TimedEvent, Timer}; +#[zenoh_macros::unstable] +use { + async_trait::async_trait, + std::collections::hash_map::Entry, + std::collections::HashMap, + std::convert::TryFrom, + std::future::Ready, + std::sync::{Arc, Mutex}, + std::time::Duration, + zenoh::handlers::{locked, DefaultHandler}, + zenoh::internal::zlock, + zenoh::pubsub::Subscriber, + zenoh::query::{QueryTarget, Reply, ReplyKeyExpr}, + zenoh::Result as ZResult, +}; + +use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR}; + +/// The builder of AdvancedSubscriber, allowing to configure it. +#[zenoh_macros::unstable] +pub struct AdvancedSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = false> { + pub(crate) session: &'a Session, + pub(crate) key_expr: ZResult>, + pub(crate) origin: Locality, + pub(crate) retransmission: bool, + pub(crate) query_target: QueryTarget, + pub(crate) query_timeout: Duration, + pub(crate) period: Option, + pub(crate) history: bool, + pub(crate) liveliness: bool, + pub(crate) handler: Handler, +} + +#[zenoh_macros::unstable] +impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { + pub(crate) fn new( + session: &'a Session, + key_expr: ZResult>, + origin: Locality, + handler: Handler, + ) -> Self { + AdvancedSubscriberBuilder { + session, + key_expr, + origin, + handler, + retransmission: false, + query_target: QueryTarget::All, + query_timeout: Duration::from_secs(10), + history: false, + liveliness: false, + period: None, + } + } +} + +#[zenoh_macros::unstable] +impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { + /// Add callback to AdvancedSubscriber. + #[inline] + pub fn callback( + self, + callback: Callback, + ) -> AdvancedSubscriberBuilder<'a, 'b, Callback> + where + Callback: Fn(Sample) + Send + Sync + 'static, + { + AdvancedSubscriberBuilder { + session: self.session, + key_expr: self.key_expr.map(|s| s.into_owned()), + origin: self.origin, + retransmission: self.retransmission, + query_target: self.query_target, + query_timeout: self.query_timeout, + period: self.period, + history: self.history, + liveliness: self.liveliness, + handler: callback, + } + } + + /// Add callback to `AdvancedSubscriber`. + /// + /// Using this guarantees that your callback will never be called concurrently. + /// If your callback is also accepted by the [`callback`](AdvancedSubscriberBuilder::callback) method, we suggest you use it instead of `callback_mut` + #[inline] + pub fn callback_mut( + self, + callback: CallbackMut, + ) -> AdvancedSubscriberBuilder<'a, 'b, impl Fn(Sample) + Send + Sync + 'static> + where + CallbackMut: FnMut(Sample) + Send + Sync + 'static, + { + self.callback(locked(callback)) + } + + /// Make the built AdvancedSubscriber an [`AdvancedSubscriber`](AdvancedSubscriber). + #[inline] + pub fn with(self, handler: Handler) -> AdvancedSubscriberBuilder<'a, 'b, Handler> + where + Handler: IntoHandler, + { + AdvancedSubscriberBuilder { + session: self.session, + key_expr: self.key_expr.map(|s| s.into_owned()), + origin: self.origin, + retransmission: self.retransmission, + query_target: self.query_target, + query_timeout: self.query_timeout, + period: self.period, + history: self.history, + liveliness: self.liveliness, + handler, + } + } +} + +#[zenoh_macros::unstable] +impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { + /// Restrict the matching publications that will be receive by this [`Subscriber`] + /// to the ones that have the given [`Locality`](crate::prelude::Locality). + #[zenoh_macros::unstable] + #[inline] + pub fn allowed_origin(mut self, origin: Locality) -> Self { + self.origin = origin; + self + } + + /// Ask for retransmission of detected lost Samples. + /// + /// Retransmission can only be achieved by Publishers that also activate retransmission. + #[zenoh_macros::unstable] + #[inline] + pub fn retransmission(mut self) -> Self { + self.retransmission = true; + self + } + + // /// Change the target to be used for queries. + + // #[inline] + // pub fn query_target(mut self, query_target: QueryTarget) -> Self { + // self.query_target = query_target; + // self + // } + + /// Change the timeout to be used for queries (history, retransmission). + #[zenoh_macros::unstable] + #[inline] + pub fn query_timeout(mut self, query_timeout: Duration) -> Self { + self.query_timeout = query_timeout; + self + } + + /// Enable periodic queries for not yet received Samples and specify their period. + /// + /// This allows to retrieve the last Sample(s) if the last Sample(s) is/are lost. + /// So it is useful for sporadic publications but useless for periodic publications + /// with a period smaller or equal to this period. + /// Retransmission can only be achieved by Publishers that also activate retransmission. + #[zenoh_macros::unstable] + #[inline] + pub fn periodic_queries(mut self, period: Option) -> Self { + self.period = period; + self + } + + /// Enable query for historical data. + /// + /// History can only be retransmitted by Publishers that also activate history. + #[zenoh_macros::unstable] + #[inline] + pub fn history(mut self) -> Self { + self.history = true; + self + } + + /// Enable detection of late joiner publishers and query for their historical data. + /// + /// Let joiner detectiopn can only be achieved for Publishers that also activate late_joiner. + /// History can only be retransmitted by Publishers that also activate history. + #[zenoh_macros::unstable] + #[inline] + pub fn late_joiner(mut self) -> Self { + self.liveliness = true; + self + } + + fn with_static_keys(self) -> AdvancedSubscriberBuilder<'a, 'static, Handler> { + AdvancedSubscriberBuilder { + session: self.session, + key_expr: self.key_expr.map(|s| s.into_owned()), + origin: self.origin, + retransmission: self.retransmission, + query_target: self.query_target, + query_timeout: self.query_timeout, + period: self.period, + history: self.history, + liveliness: self.liveliness, + handler: self.handler, + } + } +} + +impl Resolvable for AdvancedSubscriberBuilder<'_, '_, Handler> +where + Handler: IntoHandler, + Handler::Handler: Send, +{ + type To = ZResult>; +} + +impl Wait for AdvancedSubscriberBuilder<'_, '_, Handler> +where + Handler: IntoHandler + Send, + Handler::Handler: Send, +{ + fn wait(self) -> ::To { + AdvancedSubscriber::new(self.with_static_keys()) + } +} + +impl IntoFuture for AdvancedSubscriberBuilder<'_, '_, Handler> +where + Handler: IntoHandler + Send, + Handler::Handler: Send, +{ + type Output = ::To; + type IntoFuture = Ready<::To>; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} + +#[zenoh_macros::unstable] +struct InnerState { + last_seq_num: Option, + pending_queries: u64, + pending_samples: HashMap, +} + +#[zenoh_macros::unstable] +pub struct AdvancedSubscriber { + _subscriber: Subscriber<()>, + receiver: Receiver, + _liveliness_subscriber: Option>, +} + +#[zenoh_macros::unstable] +impl std::ops::Deref for AdvancedSubscriber { + type Target = Receiver; + fn deref(&self) -> &Self::Target { + &self.receiver + } +} + +#[zenoh_macros::unstable] +impl std::ops::DerefMut for AdvancedSubscriber { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.receiver + } +} + +#[zenoh_macros::unstable] +fn handle_sample( + states: &mut HashMap, + wait: bool, + sample: Sample, + callback: &Callback, +) -> bool { + if let (Some(source_id), Some(source_sn)) = ( + sample.source_info().source_id(), + sample.source_info().source_sn(), + ) { + let entry = states.entry(*source_id); + let new = matches!(&entry, Entry::Occupied(_)); + let state = entry.or_insert(InnerState { + last_seq_num: None, + pending_queries: 0, + pending_samples: HashMap::new(), + }); + if wait { + state.pending_samples.insert(source_sn, sample); + } else if state.last_seq_num.is_some() && source_sn != state.last_seq_num.unwrap() + 1 { + if source_sn > state.last_seq_num.unwrap() { + state.pending_samples.insert(source_sn, sample); + } + } else { + callback.call(sample); + let mut last_seq_num = source_sn; + state.last_seq_num = Some(last_seq_num); + while let Some(s) = state.pending_samples.remove(&(last_seq_num + 1)) { + callback.call(s); + last_seq_num += 1; + state.last_seq_num = Some(last_seq_num); + } + } + new + } else { + callback.call(sample); + true + } +} + +#[zenoh_macros::unstable] +fn seq_num_range(start: Option, end: Option) -> String { + match (start, end) { + (Some(start), Some(end)) => format!("_sn={}..{}", start, end), + (Some(start), None) => format!("_sn={}..", start), + (None, Some(end)) => format!("_sn=..{}", end), + (None, None) => "_sn=..".to_string(), + } +} + +#[zenoh_macros::unstable] +#[derive(Clone)] +struct PeriodicQuery { + source_id: Option, + statesref: Arc, bool)>>, + key_expr: KeyExpr<'static>, + session: Session, + query_target: QueryTarget, + query_timeout: Duration, + callback: Callback, +} + +#[zenoh_macros::unstable] +impl PeriodicQuery { + fn with_source_id(mut self, source_id: EntityGlobalId) -> Self { + self.source_id = Some(source_id); + self + } +} + +#[zenoh_macros::unstable] +#[async_trait] +impl Timed for PeriodicQuery { + async fn run(&mut self) { + let mut lock = zlock!(self.statesref); + let (states, _wait) = &mut *lock; + if let Some(source_id) = &self.source_id { + if let Some(state) = states.get_mut(source_id) { + state.pending_queries += 1; + let query_expr = KE_PREFIX + / &source_id.zid().into_keyexpr() + / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() + / &self.key_expr; + let seq_num_range = seq_num_range(Some(state.last_seq_num.unwrap() + 1), None); + drop(lock); + let handler = RepliesHandler { + source_id: *source_id, + statesref: self.statesref.clone(), + callback: self.callback.clone(), + }; + let _ = self + .session + .get(Selector::from((query_expr, seq_num_range))) + .callback({ + let key_expr = self.key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let (ref mut states, wait) = &mut *zlock!(handler.statesref); + handle_sample(states, *wait, s, &handler.callback); + } + } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(self.query_target) + .timeout(self.query_timeout) + .wait(); + } + } + } +} + +#[zenoh_macros::unstable] +impl AdvancedSubscriber { + fn new(conf: AdvancedSubscriberBuilder<'_, '_, H>) -> ZResult + where + H: IntoHandler + Send, + { + let statesref = Arc::new(Mutex::new((HashMap::new(), conf.history))); + let (callback, receiver) = conf.handler.into_handler(); + let key_expr = conf.key_expr?; + let retransmission = conf.retransmission; + let query_target = conf.query_target; + let query_timeout = conf.query_timeout; + let session = conf.session.clone(); + let periodic_query = conf.period.map(|period| { + ( + Arc::new(Timer::new(false)), + period, + PeriodicQuery { + source_id: None, + statesref: statesref.clone(), + key_expr: key_expr.clone().into_owned(), + session, + query_target, + query_timeout, + callback: callback.clone(), + }, + ) + }); + + let sub_callback = { + let statesref = statesref.clone(); + let session = conf.session.clone(); + let callback = callback.clone(); + let key_expr = key_expr.clone().into_owned(); + let periodic_query = periodic_query.clone(); + + move |s: Sample| { + let mut lock = zlock!(statesref); + let (states, wait) = &mut *lock; + let source_id = s.source_info().source_id().cloned(); + let new = handle_sample(states, *wait, s, &callback); + + if let Some(source_id) = source_id { + if new { + if let Some((timer, period, query)) = periodic_query.as_ref() { + timer.add(TimedEvent::periodic( + *period, + query.clone().with_source_id(source_id), + )) + } + } + + if let Some(state) = states.get_mut(&source_id) { + if retransmission + && state.pending_queries == 0 + && !state.pending_samples.is_empty() + { + state.pending_queries += 1; + let query_expr = KE_PREFIX + / &source_id.zid().into_keyexpr() + / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() + / &key_expr; + let seq_num_range = + seq_num_range(Some(state.last_seq_num.unwrap() + 1), None); + drop(lock); + let handler = RepliesHandler { + source_id, + statesref: statesref.clone(), + callback: callback.clone(), + }; + let _ = session + .get(Selector::from((query_expr, seq_num_range))) + .callback({ + let key_expr = key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let (ref mut states, wait) = + &mut *zlock!(handler.statesref); + handle_sample(states, *wait, s, &handler.callback); + } + } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); + } + } + } + } + }; + + let subscriber = conf + .session + .declare_subscriber(&key_expr) + .callback(sub_callback) + .allowed_origin(conf.origin) + .wait()?; + + if conf.history { + let handler = InitialRepliesHandler { + statesref: statesref.clone(), + callback: callback.clone(), + periodic_query, + }; + let _ = conf + .session + .get(Selector::from(( + KE_PREFIX / KE_STAR / KE_STAR / &key_expr, + "0..", + ))) + .callback({ + let key_expr = key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let (ref mut states, wait) = &mut *zlock!(handler.statesref); + handle_sample(states, *wait, s, &handler.callback); + } + } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); + } + + let liveliness_subscriber = if conf.history && conf.liveliness { + let live_callback = { + let session = conf.session.clone(); + let statesref = statesref.clone(); + let key_expr = key_expr.clone().into_owned(); + move |s: Sample| { + if let Ok(parsed) = ke_liveliness::parse(s.key_expr().as_keyexpr()) { + if let Ok(zid) = ZenohId::from_str(parsed.zid().as_str()) { + if let Ok(eid) = EntityId::from_str(parsed.eid().as_str()) { + let handler = RepliesHandler { + source_id: EntityGlobalId::new(zid, eid), + statesref: statesref.clone(), + callback: callback.clone(), + }; + let _ = session + .get(Selector::from((s.key_expr(), "0.."))) + .callback({ + let key_expr = key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let (ref mut states, wait) = + &mut *zlock!(handler.statesref); + handle_sample( + states, + *wait, + s, + &handler.callback, + ); + } + } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); + } + } + } else { + tracing::warn!( + "Received malformed liveliness token key expression: {}", + s.key_expr() + ); + } + } + }; + + Some( + conf + .session + .liveliness() + .declare_subscriber(KE_PREFIX / KE_STAR / KE_STAR / &key_expr) + // .declare_subscriber(keformat!(ke_liveliness_all::formatter(), zid = 0, eid = 0, remaining = key_expr).unwrap()) + .callback(live_callback) + .wait()?, + ) + } else { + None + }; + + let reliable_subscriber = AdvancedSubscriber { + _subscriber: subscriber, + receiver, + _liveliness_subscriber: liveliness_subscriber, + }; + + Ok(reliable_subscriber) + } + + /// Close this AdvancedSubscriber + #[inline] + pub fn close(self) -> impl Resolve> { + self._subscriber.undeclare() + } +} + +#[zenoh_macros::unstable] +#[derive(Clone)] +struct InitialRepliesHandler { + statesref: Arc, bool)>>, + periodic_query: Option<(Arc, Duration, PeriodicQuery)>, + callback: Callback, +} + +#[zenoh_macros::unstable] +impl Drop for InitialRepliesHandler { + fn drop(&mut self) { + let (states, wait) = &mut *zlock!(self.statesref); + for (source_id, state) in states.iter_mut() { + let mut pending_samples = state + .pending_samples + .drain() + .collect::>(); + pending_samples.sort_by_key(|(k, _s)| *k); + for (seq_num, sample) in pending_samples { + state.last_seq_num = Some(seq_num); + self.callback.call(sample); + } + if let Some((timer, period, query)) = self.periodic_query.as_ref() { + timer.add(TimedEvent::periodic( + *period, + query.clone().with_source_id(*source_id), + )) + } + } + *wait = false; + } +} + +#[zenoh_macros::unstable] +#[derive(Clone)] +struct RepliesHandler { + source_id: EntityGlobalId, + statesref: Arc, bool)>>, + callback: Callback, +} + +#[zenoh_macros::unstable] +impl Drop for RepliesHandler { + fn drop(&mut self) { + let (states, wait) = &mut *zlock!(self.statesref); + if let Some(state) = states.get_mut(&self.source_id) { + state.pending_queries = state.pending_queries.saturating_sub(1); + if !state.pending_samples.is_empty() && !*wait { + tracing::error!("Sample missed: unable to retrieve some missing samples."); + let mut pending_samples = state + .pending_samples + .drain() + .collect::>(); + pending_samples.sort_by_key(|(k, _s)| *k); + for (seq_num, sample) in pending_samples { + state.last_seq_num = Some(seq_num); + self.callback.call(sample); + } + } + } + } +} diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 4bab50804e..3a1046fd87 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -12,10 +12,18 @@ // ZettaScale Zenoh Team, // #[cfg(feature = "unstable")] +mod advanced_cache; +#[cfg(feature = "unstable")] +mod advanced_publisher; +#[cfg(feature = "unstable")] +mod advanced_subscriber; +#[cfg(feature = "unstable")] pub mod group; #[cfg(feature = "unstable")] mod publication_cache; #[cfg(feature = "unstable")] +mod publisher_ext; +#[cfg(feature = "unstable")] mod querying_subscriber; mod serialization; #[cfg(feature = "unstable")] @@ -31,11 +39,15 @@ pub use crate::serialization::{ }; #[cfg(feature = "unstable")] pub use crate::{ + advanced_cache::{AdvancedCache, AdvancedCacheBuilder}, + advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder, Sequencing}, + advanced_subscriber::{AdvancedSubscriber, AdvancedSubscriberBuilder}, publication_cache::{PublicationCache, PublicationCacheBuilder}, + publisher_ext::PublisherBuilderExt, querying_subscriber::{ ExtractSample, FetchingSubscriber, FetchingSubscriberBuilder, KeySpace, LivelinessSpace, QueryingSubscriberBuilder, UserSpace, }, session_ext::SessionExt, - subscriber_ext::{SubscriberBuilderExt, SubscriberForward}, + subscriber_ext::{DataSubscriberBuilderExt, SubscriberBuilderExt, SubscriberForward}, }; diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs new file mode 100644 index 0000000000..b13cf29b68 --- /dev/null +++ b/zenoh-ext/src/publisher_ext.rs @@ -0,0 +1,46 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use zenoh::pubsub::PublisherBuilder; + +use crate::AdvancedPublisherBuilder; + +/// Some extensions to the [`zenoh::publication::PublisherBuilder`](zenoh::publication::PublisherBuilder) +#[zenoh_macros::unstable] +pub trait PublisherBuilderExt<'a, 'b> { + /// Allow matching Subscribers to detect lost samples and ask for retransimission. + /// + /// Retransmission can only be achieved if history is enabled. + fn history(self, history: usize) -> AdvancedPublisherBuilder<'a, 'b>; + + /// Allow this publisher to be detected by subscribers. + /// + /// This allows Subscribers to retrieve the local history. + fn late_joiner(self) -> AdvancedPublisherBuilder<'a, 'b>; +} + +impl<'a, 'b> PublisherBuilderExt<'a, 'b> for PublisherBuilder<'a, 'b> { + /// Allow matching Subscribers to detect lost samples and ask for retransimission. + /// + /// Retransmission can only be achieved if history is enabled. + fn history(self, history: usize) -> AdvancedPublisherBuilder<'a, 'b> { + AdvancedPublisherBuilder::new(self.session, self.key_expr).history(history) + } + + /// Allow this publisher to be detected by subscribers. + /// + /// This allows Subscribers to retrieve the local history. + fn late_joiner(self) -> AdvancedPublisherBuilder<'a, 'b> { + AdvancedPublisherBuilder::new(self.session, self.key_expr).late_joiner() + } +} diff --git a/zenoh-ext/src/session_ext.rs b/zenoh-ext/src/session_ext.rs index 9d3c430aaf..8c24ccc9e2 100644 --- a/zenoh-ext/src/session_ext.rs +++ b/zenoh-ext/src/session_ext.rs @@ -15,6 +15,7 @@ use zenoh::{key_expr::KeyExpr, session::Session, Error}; use super::PublicationCacheBuilder; +use crate::advanced_cache::AdvancedCacheBuilder; /// Some extensions to the [`zenoh::Session`](zenoh::Session) #[zenoh_macros::unstable] @@ -44,6 +45,14 @@ pub trait SessionExt { where TryIntoKeyExpr: TryInto>, >>::Error: Into; + + fn declare_advanced_cache<'a, 'b, 'c, TryIntoKeyExpr>( + &'a self, + pub_key_expr: TryIntoKeyExpr, + ) -> AdvancedCacheBuilder<'a, 'b, 'c> + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into; } impl SessionExt for Session { @@ -57,4 +66,15 @@ impl SessionExt for Session { { PublicationCacheBuilder::new(self, pub_key_expr.try_into().map_err(Into::into)) } + + fn declare_advanced_cache<'a, 'b, 'c, TryIntoKeyExpr>( + &'a self, + pub_key_expr: TryIntoKeyExpr, + ) -> AdvancedCacheBuilder<'a, 'b, 'c> + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + AdvancedCacheBuilder::new(self, pub_key_expr.try_into().map_err(Into::into)) + } } diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 8d931726c3..893f23325c 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -24,7 +24,8 @@ use zenoh::{ }; use crate::{ - querying_subscriber::QueryingSubscriberBuilder, ExtractSample, FetchingSubscriberBuilder, + querying_subscriber::QueryingSubscriberBuilder, AdvancedSubscriberBuilder, ExtractSample, + FetchingSubscriberBuilder, }; /// Allows writing `subscriber.forward(receiver)` instead of `subscriber.stream().map(Ok).forward(publisher)` @@ -123,6 +124,25 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { fn querying(self) -> QueryingSubscriberBuilder<'a, 'b, Self::KeySpace, Handler>; } +/// Some extensions to the [`zenoh::subscriber::SubscriberBuilder`](zenoh::pubsub::SubscriberBuilder) +pub trait DataSubscriberBuilderExt<'a, 'b, Handler> { + /// Enable query for historical data. + /// + /// History can only be retransmitted by Publishers that also activate history. + fn history(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + + /// Ask for retransmission of detected lost Samples. + /// + /// Retransmission can only be achieved by Publishers that also activate retransmission. + fn retransmission(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + + /// Enable detection of late joiner publishers and query for their historical data. + /// + /// Let joiner detectiopn can only be achieved for Publishers that also activate late_joiner. + /// History can only be retransmitted by Publishers that also activate history. + fn late_joiner(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; +} + impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilder<'a, 'b, Handler> { type KeySpace = crate::UserSpace; @@ -227,6 +247,35 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilde } } +impl<'a, 'b, Handler> DataSubscriberBuilderExt<'a, 'b, Handler> + for SubscriberBuilder<'a, 'b, Handler> +{ + /// Enable query for historical data. + /// + /// History can only be retransmitted by Publishers that also activate history. + fn history(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) + .history() + } + + /// Ask for retransmission of detected lost Samples. + /// + /// Retransmission can only be achieved by Publishers that also activate retransmission. + fn retransmission(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) + .retransmission() + } + + /// Enable detection of late joiner publishers and query for their historical data. + /// + /// Let joiner detectiopn can only be achieved for Publishers that also activate late_joiner. + /// History can only be retransmitted by Publishers that also activate history. + fn late_joiner(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) + .late_joiner() + } +} + impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for LivelinessSubscriberBuilder<'a, 'b, Handler> { diff --git a/zenoh/src/api/builders/publisher.rs b/zenoh/src/api/builders/publisher.rs index ac6565cd27..b9580df03e 100644 --- a/zenoh/src/api/builders/publisher.rs +++ b/zenoh/src/api/builders/publisher.rs @@ -283,8 +283,16 @@ impl IntoFuture for PublicationBuilder, PublicationBuil #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] #[derive(Debug)] pub struct PublisherBuilder<'a, 'b> { + #[cfg(feature = "internal")] + pub session: &'a Session, + #[cfg(not(feature = "internal"))] pub(crate) session: &'a Session, + + #[cfg(feature = "internal")] + pub key_expr: ZResult>, + #[cfg(not(feature = "internal"))] pub(crate) key_expr: ZResult>, + pub(crate) encoding: Encoding, pub(crate) congestion_control: CongestionControl, pub(crate) priority: Priority, From 9f076a7e83e855070b3c2eda18e9f9bd28fc1837 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 8 Nov 2024 16:49:53 +0100 Subject: [PATCH 04/88] Fix doctests --- zenoh-ext/src/advanced_publisher.rs | 54 ++++------------------------- 1 file changed, 6 insertions(+), 48 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 1e6bd77d3f..28d8f9df14 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -192,9 +192,8 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh::prelude::*; /// - /// let session = zenoh::open(zenoh::config::peer()).await.unwrap(); + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression") /// .await /// .unwrap(); @@ -228,51 +227,14 @@ impl<'a> AdvancedPublisher<'a> { self.publisher.priority() } - /// Consumes the given `Publisher`, returning a thread-safe reference-counting - /// pointer to it (`Arc`). This is equivalent to `Arc::new(Publisher)`. - /// - /// This is useful to share ownership of the `Publisher` between several threads - /// and tasks. It also allows to create [`MatchingListener`] with static - /// lifetime that can be moved to several threads and tasks. - /// - /// Note: the given zenoh `Publisher` will be undeclared when the last reference to - /// it is dropped. - /// - /// # Examples - /// ```no_run - /// # #[tokio::main] - /// # async fn main() { - /// use zenoh::prelude::*; - /// - /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); - /// let publisher = session.declare_publisher("key/expression").await.unwrap().into_arc(); - /// let matching_listener = publisher.matching_listener().await.unwrap(); - /// - /// tokio::task::spawn(async move { - /// while let Ok(matching_status) = matching_listener.recv_async().await { - /// if matching_status.matching_subscribers() { - /// println!("Publisher has matching subscribers."); - /// } else { - /// println!("Publisher has NO MORE matching subscribers."); - /// } - /// } - /// }).await; - /// # } - /// ``` - #[zenoh_macros::unstable] - pub fn into_arc(self) -> std::sync::Arc { - std::sync::Arc::new(self) - } - /// Put data. /// /// # Examples /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh::prelude::*; /// - /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").await.unwrap(); /// publisher.put("value").await.unwrap(); /// # } @@ -300,9 +262,8 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh::prelude::*; /// - /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").await.unwrap(); /// publisher.delete().await.unwrap(); /// # } @@ -327,9 +288,8 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh::prelude::*; /// - /// let session = zenoh::open(zenoh::config::peer()).await.unwrap().into_arc(); + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").await.unwrap(); /// let matching_subscribers: bool = publisher /// .matching_status() @@ -352,9 +312,8 @@ impl<'a> AdvancedPublisher<'a> { /// ```no_run /// # #[tokio::main] /// # async fn main() { - /// use zenoh::prelude::*; /// - /// let session = zenoh::open(zenoh::config::peer()).await.unwrap(); + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").await.unwrap(); /// let matching_listener = publisher.matching_listener().await.unwrap(); /// while let Ok(matching_status) = matching_listener.recv_async().await { @@ -379,9 +338,8 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh::prelude::*; /// - /// let session = zenoh::open(zenoh::config::peer()).await.unwrap(); + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").await.unwrap(); /// publisher.undeclare().await.unwrap(); /// # } From 3935974eae037925363f4c3b74cf54e049fa5629 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 8 Nov 2024 18:10:22 +0100 Subject: [PATCH 05/88] Fix doc warnings --- zenoh-ext/src/advanced_cache.rs | 4 ++-- zenoh-ext/src/advanced_publisher.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index b96b7fe4af..8807b7fd57 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -83,7 +83,7 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { } /// Restrict the matching publications that will be cached by this [`AdvancedCache`] - /// to the ones that have the given [`Locality`](crate::prelude::Locality). + /// to the ones that have the given [`Locality`](zenoh::sample::Locality). #[inline] pub fn subscriber_allowed_origin(mut self, origin: Locality) -> Self { self.subscriber_origin = origin; @@ -91,7 +91,7 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { } /// Restrict the matching queries that will be receive by this [`AdvancedCache`]'s queryable - /// to the ones that have the given [`Locality`](crate::prelude::Locality). + /// to the ones that have the given [`Locality`](zenoh::sample::Locality). #[inline] pub fn queryable_allowed_origin(mut self, origin: Locality) -> Self { self.queryable_origin = origin; diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 28d8f9df14..e432e24b11 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -279,10 +279,10 @@ impl<'a> AdvancedPublisher<'a> { delete } - /// Return the [`MatchingStatus`] of the publisher. + /// Return the [`MatchingStatus`](zenoh::pubsub::MatchingStatus) of the publisher. /// - /// [`MatchingStatus::matching_subscribers`] will return true if there exist Subscribers - /// matching the Publisher's key expression and false otherwise. + /// [`MatchingStatus::matching_subscribers`](zenoh::pubsub::MatchingStatus::matching_subscribers) + /// will return true if there exist Subscribers matching the Publisher's key expression and false otherwise. /// /// # Examples /// ``` @@ -303,10 +303,10 @@ impl<'a> AdvancedPublisher<'a> { self.publisher.matching_status() } - /// Return a [`MatchingListener`] for this Publisher. + /// Return a [`MatchingListener`](zenoh::pubsub::MatchingStatus) for this Publisher. /// - /// The [`MatchingListener`] that will send a notification each time the [`MatchingStatus`] of - /// the Publisher changes. + /// The [`MatchingListener`](zenoh::pubsub::MatchingStatus) that will send a notification each time + /// the [`MatchingStatus`](zenoh::pubsub::MatchingStatus) of the Publisher changes. /// /// # Examples /// ```no_run From 405d76e7d3dc70ababeb51b1e53df3df18532d02 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 13:52:26 +0100 Subject: [PATCH 06/88] Remove debug trace --- zenoh-ext/src/advanced_publisher.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index e432e24b11..838167fb2b 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -244,7 +244,6 @@ impl<'a> AdvancedPublisher<'a> { where IntoZBytes: Into, { - println!("here"); let mut put = self.publisher.put(payload); if let Some(seqnum) = &self.seqnum { println!("there"); From 71b22d37ca4302613dd1cd5c8b2186b805ded43f Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 14:02:25 +0100 Subject: [PATCH 07/88] Add history test --- zenoh-ext/tests/advanced.rs | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 zenoh-ext/tests/advanced.rs diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs new file mode 100644 index 0000000000..90a1aa32ab --- /dev/null +++ b/zenoh-ext/tests/advanced.rs @@ -0,0 +1,96 @@ +// +// Copyright (c) 2024 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use zenoh::sample::SampleKind; +use zenoh_config::{EndPoint, WhatAmI}; +use zenoh_ext::{DataSubscriberBuilderExt, PublisherBuilderExt}; + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_advanced_history() { + use std::time::Duration; + + use zenoh::internal::ztimeout; + + const TIMEOUT: Duration = Duration::from_secs(60); + const SLEEP: Duration = Duration::from_secs(1); + const PEER1_ENDPOINT: &str = "tcp/localhost:47450"; + + const ADVANCED_HISTORY_KEYEXPR: &str = "test/advanced/history"; + + zenoh_util::init_log_from_env_or("debug"); + + let peer1 = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![PEER1_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Peer)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Peer (1) ZID: {}", s.zid()); + s + }; + + let publ = ztimeout!(peer1.declare_publisher(ADVANCED_HISTORY_KEYEXPR).history(3)).unwrap(); + ztimeout!(publ.put("1")).unwrap(); + ztimeout!(publ.put("2")).unwrap(); + ztimeout!(publ.put("3")).unwrap(); + ztimeout!(publ.put("4")).unwrap(); + + tokio::time::sleep(SLEEP).await; + + let peer2 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![PEER1_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Peer)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Peer (2) ZID: {}", s.zid()); + s + }; + + let sub = ztimeout!(peer2.declare_subscriber(ADVANCED_HISTORY_KEYEXPR).history()).unwrap(); + tokio::time::sleep(SLEEP).await; + + ztimeout!(publ.put("5")).unwrap(); + tokio::time::sleep(SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "2"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "3"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "4"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "5"); + + assert!(sub.try_recv().unwrap().is_none()); + + publ.undeclare().await.unwrap(); + // sub.undeclare().await.unwrap(); + + peer1.close().await.unwrap(); + peer2.close().await.unwrap(); +} From af1b2a2e8c20f997ba5453130ec6984a3eee5279 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 22:02:36 +0100 Subject: [PATCH 08/88] Fix periodic queries --- zenoh-ext/src/advanced_subscriber.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 92084181cd..e44007c1da 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -299,7 +299,7 @@ fn handle_sample( sample.source_info().source_sn(), ) { let entry = states.entry(*source_id); - let new = matches!(&entry, Entry::Occupied(_)); + let new = matches!(&entry, Entry::Vacant(_)); let state = entry.or_insert(InnerState { last_seq_num: None, pending_queries: 0, From bd3235624ea615dae852e230b35039e375508d10 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 22:02:50 +0100 Subject: [PATCH 09/88] Remove debug trace --- zenoh-ext/src/advanced_publisher.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 838167fb2b..bb0a701957 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -246,7 +246,6 @@ impl<'a> AdvancedPublisher<'a> { { let mut put = self.publisher.put(payload); if let Some(seqnum) = &self.seqnum { - println!("there"); put = put.source_info(SourceInfo::new( Some(self.publisher.id()), Some(seqnum.fetch_add(1, Ordering::Relaxed)), From 4e4bbb663d682243a66286c888643c1edfc3bbc1 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 22:03:36 +0100 Subject: [PATCH 10/88] Lower test debug level --- zenoh-ext/tests/advanced.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index 90a1aa32ab..f9f85f4b55 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -28,7 +28,7 @@ async fn test_advanced_history() { const ADVANCED_HISTORY_KEYEXPR: &str = "test/advanced/history"; - zenoh_util::init_log_from_env_or("debug"); + zenoh_util::init_log_from_env_or("error"); let peer1 = { let mut c = zenoh::Config::default(); From f36c890f4ec7cf19351dce5149a2fb399f85fcab Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 22:05:53 +0100 Subject: [PATCH 11/88] Add retransmission tests --- zenoh-ext/tests/advanced.rs | 252 ++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index f9f85f4b55..4c945baa06 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -94,3 +94,255 @@ async fn test_advanced_history() { peer1.close().await.unwrap(); peer2.close().await.unwrap(); } + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_advanced_retransmission() { + use std::time::Duration; + + use zenoh::internal::ztimeout; + + const TIMEOUT: Duration = Duration::from_secs(60); + const SLEEP: Duration = Duration::from_secs(1); + const RECONNECT_SLEEP: Duration = Duration::from_secs(5); + const ROUTER_ENDPOINT: &str = "tcp/localhost:47451"; + + const ADVANCED_RETRANSMISSION_KEYEXPR: &str = "test/advanced/retransmission"; + + zenoh_util::init_log_from_env_or("error"); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + + let client1 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (1) ZID: {}", s.zid()); + s + }; + + let client2 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (2) ZID: {}", s.zid()); + s + }; + + let sub = ztimeout!(client2 + .declare_subscriber(ADVANCED_RETRANSMISSION_KEYEXPR) + .retransmission()) + .unwrap(); + tokio::time::sleep(SLEEP).await; + + let publ = ztimeout!(client1 + .declare_publisher(ADVANCED_RETRANSMISSION_KEYEXPR) + .history(10) + .retransmission()) + .unwrap(); + ztimeout!(publ.put("1")).unwrap(); + + tokio::time::sleep(SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "1"); + + assert!(sub.try_recv().unwrap().is_none()); + + router.close().await.unwrap(); + tokio::time::sleep(SLEEP).await; + + ztimeout!(publ.put("2")).unwrap(); + ztimeout!(publ.put("3")).unwrap(); + ztimeout!(publ.put("4")).unwrap(); + tokio::time::sleep(SLEEP).await; + + assert!(sub.try_recv().unwrap().is_none()); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + tokio::time::sleep(RECONNECT_SLEEP).await; + + ztimeout!(publ.put("5")).unwrap(); + tokio::time::sleep(SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "2"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "3"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "4"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "5"); + + assert!(sub.try_recv().unwrap().is_none()); + + publ.undeclare().await.unwrap(); + // sub.undeclare().await.unwrap(); + + client1.close().await.unwrap(); + client2.close().await.unwrap(); + + router.close().await.unwrap(); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_advanced_retransmission_periodic() { + use std::time::Duration; + + use zenoh::internal::ztimeout; + + const TIMEOUT: Duration = Duration::from_secs(60); + const SLEEP: Duration = Duration::from_secs(1); + const RECONNECT_SLEEP: Duration = Duration::from_secs(8); + const ROUTER_ENDPOINT: &str = "tcp/localhost:47452"; + + const ADVANCED_RETRANSMISSION_PERIODIC_KEYEXPR: &str = "test/advanced/retransmission/periodic"; + + zenoh_util::init_log_from_env_or("error"); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + + let client1 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (1) ZID: {}", s.zid()); + s + }; + + let client2 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (2) ZID: {}", s.zid()); + s + }; + + let sub = ztimeout!(client2 + .declare_subscriber(ADVANCED_RETRANSMISSION_PERIODIC_KEYEXPR) + .retransmission() + .periodic_queries(Some(Duration::from_secs(1)))) + .unwrap(); + tokio::time::sleep(SLEEP).await; + + let publ = ztimeout!(client1 + .declare_publisher(ADVANCED_RETRANSMISSION_PERIODIC_KEYEXPR) + .history(10) + .retransmission()) + .unwrap(); + ztimeout!(publ.put("1")).unwrap(); + + tokio::time::sleep(SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "1"); + + assert!(sub.try_recv().unwrap().is_none()); + + router.close().await.unwrap(); + tokio::time::sleep(SLEEP).await; + + ztimeout!(publ.put("2")).unwrap(); + ztimeout!(publ.put("3")).unwrap(); + ztimeout!(publ.put("4")).unwrap(); + tokio::time::sleep(SLEEP).await; + + assert!(sub.try_recv().unwrap().is_none()); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + tokio::time::sleep(RECONNECT_SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "2"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "3"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "4"); + + assert!(sub.try_recv().unwrap().is_none()); + + publ.undeclare().await.unwrap(); + // sub.undeclare().await.unwrap(); + + client1.close().await.unwrap(); + client2.close().await.unwrap(); + + router.close().await.unwrap(); +} From 23f145da22cd86f1815df97b001054c84c9bc871 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 22:24:04 +0100 Subject: [PATCH 12/88] Liveliness sub callback shoud increase pending queries counter --- zenoh-ext/src/advanced_subscriber.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index e44007c1da..65270bc95b 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -543,8 +543,18 @@ impl AdvancedSubscriber { if let Ok(parsed) = ke_liveliness::parse(s.key_expr().as_keyexpr()) { if let Ok(zid) = ZenohId::from_str(parsed.zid().as_str()) { if let Ok(eid) = EntityId::from_str(parsed.eid().as_str()) { + let source_id = EntityGlobalId::new(zid, eid); + let (ref mut states, _wait) = &mut *zlock!(statesref); + let entry = states.entry(source_id); + let state = entry.or_insert(InnerState { + last_seq_num: None, + pending_queries: 0, + pending_samples: HashMap::new(), + }); + state.pending_queries += 1; + let handler = RepliesHandler { - source_id: EntityGlobalId::new(zid, eid), + source_id, statesref: statesref.clone(), callback: callback.clone(), }; From de396a4fdcb952ec825e049a28c8930fe6538a51 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 22:33:14 +0100 Subject: [PATCH 13/88] Liveliness sub callback shoud spawn periodic queries when enbaled --- zenoh-ext/src/advanced_subscriber.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 65270bc95b..d5c83ada41 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -508,7 +508,7 @@ impl AdvancedSubscriber { let handler = InitialRepliesHandler { statesref: statesref.clone(), callback: callback.clone(), - periodic_query, + periodic_query: periodic_query.clone(), }; let _ = conf .session @@ -546,6 +546,7 @@ impl AdvancedSubscriber { let source_id = EntityGlobalId::new(zid, eid); let (ref mut states, _wait) = &mut *zlock!(statesref); let entry = states.entry(source_id); + let new = matches!(&entry, Entry::Vacant(_)); let state = entry.or_insert(InnerState { last_seq_num: None, pending_queries: 0, @@ -582,6 +583,15 @@ impl AdvancedSubscriber { .target(query_target) .timeout(query_timeout) .wait(); + + if new { + if let Some((timer, period, query)) = periodic_query.as_ref() { + timer.add(TimedEvent::periodic( + *period, + query.clone().with_source_id(source_id), + )) + } + } } } } else { From ff241356a7ec15550258058a38806e6403a968f7 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 13 Nov 2024 22:49:33 +0100 Subject: [PATCH 14/88] Add late_joiner test --- zenoh-ext/tests/advanced.rs | 104 ++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index 4c945baa06..432cceff80 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -346,3 +346,107 @@ async fn test_advanced_retransmission_periodic() { router.close().await.unwrap(); } + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_advanced_late_joiner() { + use std::time::Duration; + + use zenoh::internal::ztimeout; + + const TIMEOUT: Duration = Duration::from_secs(60); + const SLEEP: Duration = Duration::from_secs(1); + const RECONNECT_SLEEP: Duration = Duration::from_secs(8); + const ROUTER_ENDPOINT: &str = "tcp/localhost:47453"; + + const ADVANCED_LATE_JOINER_KEYEXPR: &str = "test/advanced/late_joiner"; + + zenoh_util::init_log_from_env_or("error"); + + let peer1 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Peer)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Peer (1) ZID: {}", s.zid()); + s + }; + + let peer2 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Peer)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Peer (2) ZID: {}", s.zid()); + s + }; + + let sub = ztimeout!(peer2 + .declare_subscriber(ADVANCED_LATE_JOINER_KEYEXPR) + .history() + .late_joiner()) + .unwrap(); + tokio::time::sleep(SLEEP).await; + + let publ = ztimeout!(peer1 + .declare_publisher(ADVANCED_LATE_JOINER_KEYEXPR) + .history(10) + .late_joiner()) + .unwrap(); + ztimeout!(publ.put("1")).unwrap(); + ztimeout!(publ.put("2")).unwrap(); + ztimeout!(publ.put("3")).unwrap(); + + tokio::time::sleep(SLEEP).await; + + assert!(sub.try_recv().unwrap().is_none()); + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + tokio::time::sleep(RECONNECT_SLEEP).await; + + ztimeout!(publ.put("4")).unwrap(); + tokio::time::sleep(SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "1"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "2"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "3"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "4"); + + assert!(sub.try_recv().unwrap().is_none()); + + publ.undeclare().await.unwrap(); + // sub.undeclare().await.unwrap(); + + peer1.close().await.unwrap(); + peer2.close().await.unwrap(); + + router.close().await.unwrap(); +} From 975aba4606fa52a0930bbbead8d3dfca1c3aa683 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 14 Nov 2024 11:41:08 +0100 Subject: [PATCH 15/88] Only treat pending samples when there are no more pending queries --- zenoh-ext/src/advanced_subscriber.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index d5c83ada41..777bea0ae5 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -679,7 +679,7 @@ impl Drop for RepliesHandler { let (states, wait) = &mut *zlock!(self.statesref); if let Some(state) = states.get_mut(&self.source_id) { state.pending_queries = state.pending_queries.saturating_sub(1); - if !state.pending_samples.is_empty() && !*wait { + if state.pending_queries == 0 && !state.pending_samples.is_empty() && !*wait { tracing::error!("Sample missed: unable to retrieve some missing samples."); let mut pending_samples = state .pending_samples From 5d9ac8d4a8ac6ff9f69e4e135b0887e01e481d00 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 14 Nov 2024 22:38:31 +0100 Subject: [PATCH 16/88] Apply proper sequencing for history --- zenoh-ext/src/advanced_publisher.rs | 1 + zenoh-ext/tests/advanced.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index bb0a701957..38af274eb0 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -79,6 +79,7 @@ impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { /// Change the history size for each resource. pub fn history(mut self, history: usize) -> Self { self.cache = true; + self.sequencing = Sequencing::Timestamp; self.history = history; self } diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index 432cceff80..f099e2b265 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -13,7 +13,7 @@ // use zenoh::sample::SampleKind; -use zenoh_config::{EndPoint, WhatAmI}; +use zenoh_config::{EndPoint, ModeDependentValue, WhatAmI}; use zenoh_ext::{DataSubscriberBuilderExt, PublisherBuilderExt}; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] @@ -37,6 +37,9 @@ async fn test_advanced_history() { .set(vec![PEER1_ENDPOINT.parse::().unwrap()]) .unwrap(); c.scouting.multicast.set_enabled(Some(false)).unwrap(); + c.timestamping + .set_enabled(Some(ModeDependentValue::Unique(true))) + .unwrap(); let _ = c.set_mode(Some(WhatAmI::Peer)); let s = ztimeout!(zenoh::open(c)).unwrap(); tracing::info!("Peer (1) ZID: {}", s.zid()); @@ -369,6 +372,9 @@ async fn test_advanced_late_joiner() { .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) .unwrap(); c.scouting.multicast.set_enabled(Some(false)).unwrap(); + c.timestamping + .set_enabled(Some(ModeDependentValue::Unique(true))) + .unwrap(); let _ = c.set_mode(Some(WhatAmI::Peer)); let s = ztimeout!(zenoh::open(c)).unwrap(); tracing::info!("Peer (1) ZID: {}", s.zid()); From 2305b4128196ead67be15345b85c10e5d8843806 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 14 Nov 2024 22:41:28 +0100 Subject: [PATCH 17/88] Improve AdvancedSubscriber --- Cargo.lock | 1 + zenoh-ext/Cargo.toml | 1 + zenoh-ext/src/advanced_cache.rs | 2 + zenoh-ext/src/advanced_publisher.rs | 13 +- zenoh-ext/src/advanced_subscriber.rs | 327 +++++++++++++++++++-------- 5 files changed, 250 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7beb1c9a7e..00242a3cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5602,6 +5602,7 @@ dependencies = [ "serde", "tokio", "tracing", + "uhlc", "zenoh", "zenoh-config", "zenoh-macros", diff --git a/zenoh-ext/Cargo.toml b/zenoh-ext/Cargo.toml index 0b693fa820..d433aa137c 100644 --- a/zenoh-ext/Cargo.toml +++ b/zenoh-ext/Cargo.toml @@ -46,6 +46,7 @@ futures = { workspace = true } tracing = { workspace = true } serde = { workspace = true, features = ["default"] } leb128 = { workspace = true } +uhlc = { workspace = true } zenoh = { workspace = true, default-features = false } zenoh-macros = { workspace = true } diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 8807b7fd57..410df20c44 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -39,6 +39,8 @@ pub(crate) static KE_STAR: &keyexpr = ke!("*"); #[zenoh_macros::unstable] pub(crate) static KE_PREFIX: &keyexpr = ke!("@cache"); #[zenoh_macros::unstable] +pub(crate) static KE_UHLC: &keyexpr = ke!("uhlc"); +#[zenoh_macros::unstable] kedefine!( pub(crate) ke_liveliness: "@cache/${zid:*}/${eid:*}/${remaining:**}", ); diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 38af274eb0..01c2264c3b 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -29,7 +29,7 @@ use zenoh::{ }; use crate::{ - advanced_cache::{AdvancedCache, KE_PREFIX}, + advanced_cache::{AdvancedCache, KE_PREFIX, KE_UHLC}, SessionExt, }; @@ -134,9 +134,14 @@ impl<'a> AdvancedPublisher<'a> { .declare_publisher(key_expr.clone().into_owned()) .wait()?; let id = publisher.id(); - let prefix = KE_PREFIX - / &id.zid().into_keyexpr() - / &KeyExpr::try_from(id.eid().to_string()).unwrap(); + let prefix = match conf.sequencing { + Sequencing::SequenceNumber => { + KE_PREFIX + / &id.zid().into_keyexpr() + / &KeyExpr::try_from(id.eid().to_string()).unwrap() + } + _ => KE_PREFIX / &id.zid().into_keyexpr() / KE_UHLC, + }; let seqnum = match conf.sequencing { Sequencing::SequenceNumber => Some(AtomicU32::new(0)), diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 777bea0ae5..3823500927 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -18,7 +18,7 @@ use zenoh::{ handlers::{Callback, IntoHandler}, key_expr::KeyExpr, query::{ConsolidationMode, Selector}, - sample::{Locality, Sample}, + sample::{Locality, Sample, SampleKind}, session::{EntityGlobalId, EntityId}, Resolvable, Resolve, Session, Wait, }; @@ -32,14 +32,16 @@ use { std::future::Ready, std::sync::{Arc, Mutex}, std::time::Duration, + uhlc::ID, zenoh::handlers::{locked, DefaultHandler}, zenoh::internal::zlock, zenoh::pubsub::Subscriber, zenoh::query::{QueryTarget, Reply, ReplyKeyExpr}, + zenoh::time::Timestamp, zenoh::Result as ZResult, }; -use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR}; +use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; /// The builder of AdvancedSubscriber, allowing to configure it. #[zenoh_macros::unstable] @@ -259,12 +261,26 @@ where } #[zenoh_macros::unstable] -struct InnerState { +struct State { + global_pending_queries: u64, + sequenced_states: HashMap, + timestamped_states: HashMap, +} + +#[zenoh_macros::unstable] +struct SequencedState { last_seq_num: Option, pending_queries: u64, pending_samples: HashMap, } +#[zenoh_macros::unstable] +struct TimestampedState { + last_timestamp: Option, + pending_queries: u64, + pending_samples: HashMap, +} + #[zenoh_macros::unstable] pub struct AdvancedSubscriber { _subscriber: Subscriber<()>, @@ -288,24 +304,19 @@ impl std::ops::DerefMut for AdvancedSubscriber { } #[zenoh_macros::unstable] -fn handle_sample( - states: &mut HashMap, - wait: bool, - sample: Sample, - callback: &Callback, -) -> bool { +fn handle_sample(states: &mut State, sample: Sample, callback: &Callback) -> bool { if let (Some(source_id), Some(source_sn)) = ( sample.source_info().source_id(), sample.source_info().source_sn(), ) { - let entry = states.entry(*source_id); + let entry = states.sequenced_states.entry(*source_id); let new = matches!(&entry, Entry::Vacant(_)); - let state = entry.or_insert(InnerState { + let state = entry.or_insert(SequencedState { last_seq_num: None, pending_queries: 0, pending_samples: HashMap::new(), }); - if wait { + if states.global_pending_queries != 0 { state.pending_samples.insert(source_sn, sample); } else if state.last_seq_num.is_some() && source_sn != state.last_seq_num.unwrap() + 1 { if source_sn > state.last_seq_num.unwrap() { @@ -322,9 +333,25 @@ fn handle_sample( } } new + } else if let Some(timestamp) = sample.timestamp() { + let entry = states.timestamped_states.entry(*timestamp.get_id()); + let state = entry.or_insert(TimestampedState { + last_timestamp: None, + pending_queries: 0, + pending_samples: HashMap::new(), + }); + if state.last_timestamp.map(|t| t < *timestamp).unwrap_or(true) { + if states.global_pending_queries == 0 && state.pending_queries == 0 { + state.last_timestamp = Some(*timestamp); + callback.call(sample); + } else { + state.pending_samples.entry(*timestamp).or_insert(sample); + } + } + false } else { callback.call(sample); - true + false } } @@ -342,7 +369,7 @@ fn seq_num_range(start: Option, end: Option) -> String { #[derive(Clone)] struct PeriodicQuery { source_id: Option, - statesref: Arc, bool)>>, + statesref: Arc>, key_expr: KeyExpr<'static>, session: Session, query_target: QueryTarget, @@ -363,9 +390,9 @@ impl PeriodicQuery { impl Timed for PeriodicQuery { async fn run(&mut self) { let mut lock = zlock!(self.statesref); - let (states, _wait) = &mut *lock; + let states = &mut *lock; if let Some(source_id) = &self.source_id { - if let Some(state) = states.get_mut(source_id) { + if let Some(state) = states.sequenced_states.get_mut(source_id) { state.pending_queries += 1; let query_expr = KE_PREFIX / &source_id.zid().into_keyexpr() @@ -373,7 +400,7 @@ impl Timed for PeriodicQuery { / &self.key_expr; let seq_num_range = seq_num_range(Some(state.last_seq_num.unwrap() + 1), None); drop(lock); - let handler = RepliesHandler { + let handler = SequencedRepliesHandler { source_id: *source_id, statesref: self.statesref.clone(), callback: self.callback.clone(), @@ -386,8 +413,8 @@ impl Timed for PeriodicQuery { move |r: Reply| { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { - let (ref mut states, wait) = &mut *zlock!(handler.statesref); - handle_sample(states, *wait, s, &handler.callback); + let states = &mut *zlock!(handler.statesref); + handle_sample(states, s, &handler.callback); } } } @@ -408,7 +435,11 @@ impl AdvancedSubscriber { where H: IntoHandler + Send, { - let statesref = Arc::new(Mutex::new((HashMap::new(), conf.history))); + let statesref = Arc::new(Mutex::new(State { + sequenced_states: HashMap::new(), + timestamped_states: HashMap::new(), + global_pending_queries: if conf.history { 1 } else { 0 }, + })); let (callback, receiver) = conf.handler.into_handler(); let key_expr = conf.key_expr?; let retransmission = conf.retransmission; @@ -440,9 +471,9 @@ impl AdvancedSubscriber { move |s: Sample| { let mut lock = zlock!(statesref); - let (states, wait) = &mut *lock; + let states = &mut *lock; let source_id = s.source_info().source_id().cloned(); - let new = handle_sample(states, *wait, s, &callback); + let new = handle_sample(states, s, &callback); if let Some(source_id) = source_id { if new { @@ -454,7 +485,7 @@ impl AdvancedSubscriber { } } - if let Some(state) = states.get_mut(&source_id) { + if let Some(state) = states.sequenced_states.get_mut(&source_id) { if retransmission && state.pending_queries == 0 && !state.pending_samples.is_empty() @@ -467,7 +498,7 @@ impl AdvancedSubscriber { let seq_num_range = seq_num_range(Some(state.last_seq_num.unwrap() + 1), None); drop(lock); - let handler = RepliesHandler { + let handler = SequencedRepliesHandler { source_id, statesref: statesref.clone(), callback: callback.clone(), @@ -479,9 +510,8 @@ impl AdvancedSubscriber { move |r: Reply| { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { - let (ref mut states, wait) = - &mut *zlock!(handler.statesref); - handle_sample(states, *wait, s, &handler.callback); + let states = &mut *zlock!(handler.statesref); + handle_sample(states, s, &handler.callback); } } } @@ -521,8 +551,8 @@ impl AdvancedSubscriber { move |r: Reply| { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { - let (ref mut states, wait) = &mut *zlock!(handler.statesref); - handle_sample(states, *wait, s, &handler.callback); + let states = &mut *zlock!(handler.statesref); + handle_sample(states, s, &handler.callback); } } } @@ -540,23 +570,98 @@ impl AdvancedSubscriber { let statesref = statesref.clone(); let key_expr = key_expr.clone().into_owned(); move |s: Sample| { - if let Ok(parsed) = ke_liveliness::parse(s.key_expr().as_keyexpr()) { - if let Ok(zid) = ZenohId::from_str(parsed.zid().as_str()) { - if let Ok(eid) = EntityId::from_str(parsed.eid().as_str()) { - let source_id = EntityGlobalId::new(zid, eid); - let (ref mut states, _wait) = &mut *zlock!(statesref); - let entry = states.entry(source_id); - let new = matches!(&entry, Entry::Vacant(_)); - let state = entry.or_insert(InnerState { - last_seq_num: None, - pending_queries: 0, - pending_samples: HashMap::new(), - }); - state.pending_queries += 1; - - let handler = RepliesHandler { - source_id, + if s.kind() == SampleKind::Put { + if let Ok(parsed) = ke_liveliness::parse(s.key_expr().as_keyexpr()) { + if let Ok(zid) = ZenohId::from_str(parsed.zid().as_str()) { + if parsed.eid() == KE_UHLC { + let states = &mut *zlock!(statesref); + let entry = states.timestamped_states.entry(ID::from(zid)); + let state = entry.or_insert(TimestampedState { + last_timestamp: None, + pending_queries: 0, + pending_samples: HashMap::new(), + }); + state.pending_queries += 1; + + let handler = TimestampedRepliesHandler { + id: ID::from(zid), + statesref: statesref.clone(), + callback: callback.clone(), + }; + let _ = session + .get(Selector::from((s.key_expr(), "0.."))) + .callback({ + let key_expr = key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let states = + &mut *zlock!(handler.statesref); + handle_sample(states, s, &handler.callback); + } + } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); + } else if let Ok(eid) = EntityId::from_str(parsed.eid().as_str()) { + let source_id = EntityGlobalId::new(zid, eid); + let states = &mut *zlock!(statesref); + let entry = states.sequenced_states.entry(source_id); + let new = matches!(&entry, Entry::Vacant(_)); + let state = entry.or_insert(SequencedState { + last_seq_num: None, + pending_queries: 0, + pending_samples: HashMap::new(), + }); + state.pending_queries += 1; + + let handler = SequencedRepliesHandler { + source_id, + statesref: statesref.clone(), + callback: callback.clone(), + }; + let _ = session + .get(Selector::from((s.key_expr(), "0.."))) + .callback({ + let key_expr = key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let states = + &mut *zlock!(handler.statesref); + handle_sample(states, s, &handler.callback); + } + } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); + + if new { + if let Some((timer, period, query)) = + periodic_query.as_ref() + { + timer.add(TimedEvent::periodic( + *period, + query.clone().with_source_id(source_id), + )) + } + } + } + } else { + let states = &mut *zlock!(statesref); + states.global_pending_queries += 1; + + let handler = InitialRepliesHandler { statesref: statesref.clone(), + periodic_query: None, callback: callback.clone(), }; let _ = session @@ -566,14 +671,8 @@ impl AdvancedSubscriber { move |r: Reply| { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { - let (ref mut states, wait) = - &mut *zlock!(handler.statesref); - handle_sample( - states, - *wait, - s, - &handler.callback, - ); + let states = &mut *zlock!(handler.statesref); + handle_sample(states, s, &handler.callback); } } } @@ -583,22 +682,13 @@ impl AdvancedSubscriber { .target(query_target) .timeout(query_timeout) .wait(); - - if new { - if let Some((timer, period, query)) = periodic_query.as_ref() { - timer.add(TimedEvent::periodic( - *period, - query.clone().with_source_id(source_id), - )) - } - } } + } else { + tracing::warn!( + "Received malformed liveliness token key expression: {}", + s.key_expr() + ); } - } else { - tracing::warn!( - "Received malformed liveliness token key expression: {}", - s.key_expr() - ); } } }; @@ -635,7 +725,7 @@ impl AdvancedSubscriber { #[zenoh_macros::unstable] #[derive(Clone)] struct InitialRepliesHandler { - statesref: Arc, bool)>>, + statesref: Arc>, periodic_query: Option<(Arc, Duration, PeriodicQuery)>, callback: Callback, } @@ -643,43 +733,68 @@ struct InitialRepliesHandler { #[zenoh_macros::unstable] impl Drop for InitialRepliesHandler { fn drop(&mut self) { - let (states, wait) = &mut *zlock!(self.statesref); - for (source_id, state) in states.iter_mut() { - let mut pending_samples = state - .pending_samples - .drain() - .collect::>(); - pending_samples.sort_by_key(|(k, _s)| *k); - for (seq_num, sample) in pending_samples { - state.last_seq_num = Some(seq_num); - self.callback.call(sample); + let states = &mut *zlock!(self.statesref); + states.global_pending_queries = states.global_pending_queries.saturating_sub(1); + + if states.global_pending_queries == 0 { + for (source_id, state) in states.sequenced_states.iter_mut() { + if state.pending_queries == 0 + && !state.pending_samples.is_empty() + && states.global_pending_queries == 0 + { + tracing::error!("Sample missed: unable to retrieve some missing samples."); + let mut pending_samples = state + .pending_samples + .drain() + .collect::>(); + pending_samples.sort_by_key(|(k, _s)| *k); + for (seq_num, sample) in pending_samples { + state.last_seq_num = Some(seq_num); + self.callback.call(sample); + } + } + if let Some((timer, period, query)) = self.periodic_query.as_ref() { + timer.add(TimedEvent::periodic( + *period, + query.clone().with_source_id(*source_id), + )) + } } - if let Some((timer, period, query)) = self.periodic_query.as_ref() { - timer.add(TimedEvent::periodic( - *period, - query.clone().with_source_id(*source_id), - )) + for state in states.timestamped_states.values_mut() { + if state.pending_queries == 0 && !state.pending_samples.is_empty() { + let mut pending_samples = state + .pending_samples + .drain() + .collect::>(); + pending_samples.sort_by_key(|(k, _s)| *k); + for (timestamp, sample) in pending_samples { + state.last_timestamp = Some(timestamp); + self.callback.call(sample); + } + } } } - *wait = false; } } #[zenoh_macros::unstable] #[derive(Clone)] -struct RepliesHandler { +struct SequencedRepliesHandler { source_id: EntityGlobalId, - statesref: Arc, bool)>>, + statesref: Arc>, callback: Callback, } #[zenoh_macros::unstable] -impl Drop for RepliesHandler { +impl Drop for SequencedRepliesHandler { fn drop(&mut self) { - let (states, wait) = &mut *zlock!(self.statesref); - if let Some(state) = states.get_mut(&self.source_id) { + let states = &mut *zlock!(self.statesref); + if let Some(state) = states.sequenced_states.get_mut(&self.source_id) { state.pending_queries = state.pending_queries.saturating_sub(1); - if state.pending_queries == 0 && !state.pending_samples.is_empty() && !*wait { + if state.pending_queries == 0 + && !state.pending_samples.is_empty() + && states.global_pending_queries == 0 + { tracing::error!("Sample missed: unable to retrieve some missing samples."); let mut pending_samples = state .pending_samples @@ -694,3 +809,35 @@ impl Drop for RepliesHandler { } } } + +#[zenoh_macros::unstable] +#[derive(Clone)] +struct TimestampedRepliesHandler { + id: ID, + statesref: Arc>, + callback: Callback, +} + +#[zenoh_macros::unstable] +impl Drop for TimestampedRepliesHandler { + fn drop(&mut self) { + let states = &mut *zlock!(self.statesref); + if let Some(state) = states.timestamped_states.get_mut(&self.id) { + state.pending_queries = state.pending_queries.saturating_sub(1); + if state.pending_queries == 0 + && !state.pending_samples.is_empty() + && states.global_pending_queries == 0 + { + let mut pending_samples = state + .pending_samples + .drain() + .collect::>(); + pending_samples.sort_by_key(|(k, _s)| *k); + for (timestamp, sample) in pending_samples { + state.last_timestamp = Some(timestamp); + self.callback.call(sample); + } + } + } + } +} From 883885bee16adfa0dbb4e874e26d0f2b85a3c236 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 14 Nov 2024 22:55:45 +0100 Subject: [PATCH 18/88] Code reorg --- zenoh-ext/src/advanced_subscriber.rs | 57 ++++++++++++---------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 3823500927..9936e85348 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -263,22 +263,15 @@ where #[zenoh_macros::unstable] struct State { global_pending_queries: u64, - sequenced_states: HashMap, - timestamped_states: HashMap, + sequenced_states: HashMap>, + timestamped_states: HashMap>, } #[zenoh_macros::unstable] -struct SequencedState { - last_seq_num: Option, +struct SourceState { + last_delivered: Option, pending_queries: u64, - pending_samples: HashMap, -} - -#[zenoh_macros::unstable] -struct TimestampedState { - last_timestamp: Option, - pending_queries: u64, - pending_samples: HashMap, + pending_samples: HashMap, } #[zenoh_macros::unstable] @@ -311,38 +304,38 @@ fn handle_sample(states: &mut State, sample: Sample, callback: &Callback ) { let entry = states.sequenced_states.entry(*source_id); let new = matches!(&entry, Entry::Vacant(_)); - let state = entry.or_insert(SequencedState { - last_seq_num: None, + let state = entry.or_insert(SourceState:: { + last_delivered: None, pending_queries: 0, pending_samples: HashMap::new(), }); if states.global_pending_queries != 0 { state.pending_samples.insert(source_sn, sample); - } else if state.last_seq_num.is_some() && source_sn != state.last_seq_num.unwrap() + 1 { - if source_sn > state.last_seq_num.unwrap() { + } else if state.last_delivered.is_some() && source_sn != state.last_delivered.unwrap() + 1 { + if source_sn > state.last_delivered.unwrap() { state.pending_samples.insert(source_sn, sample); } } else { callback.call(sample); let mut last_seq_num = source_sn; - state.last_seq_num = Some(last_seq_num); + state.last_delivered = Some(last_seq_num); while let Some(s) = state.pending_samples.remove(&(last_seq_num + 1)) { callback.call(s); last_seq_num += 1; - state.last_seq_num = Some(last_seq_num); + state.last_delivered = Some(last_seq_num); } } new } else if let Some(timestamp) = sample.timestamp() { let entry = states.timestamped_states.entry(*timestamp.get_id()); - let state = entry.or_insert(TimestampedState { - last_timestamp: None, + let state = entry.or_insert(SourceState:: { + last_delivered: None, pending_queries: 0, pending_samples: HashMap::new(), }); - if state.last_timestamp.map(|t| t < *timestamp).unwrap_or(true) { + if state.last_delivered.map(|t| t < *timestamp).unwrap_or(true) { if states.global_pending_queries == 0 && state.pending_queries == 0 { - state.last_timestamp = Some(*timestamp); + state.last_delivered = Some(*timestamp); callback.call(sample); } else { state.pending_samples.entry(*timestamp).or_insert(sample); @@ -398,7 +391,7 @@ impl Timed for PeriodicQuery { / &source_id.zid().into_keyexpr() / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() / &self.key_expr; - let seq_num_range = seq_num_range(Some(state.last_seq_num.unwrap() + 1), None); + let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); drop(lock); let handler = SequencedRepliesHandler { source_id: *source_id, @@ -496,7 +489,7 @@ impl AdvancedSubscriber { / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() / &key_expr; let seq_num_range = - seq_num_range(Some(state.last_seq_num.unwrap() + 1), None); + seq_num_range(Some(state.last_delivered.unwrap() + 1), None); drop(lock); let handler = SequencedRepliesHandler { source_id, @@ -576,8 +569,8 @@ impl AdvancedSubscriber { if parsed.eid() == KE_UHLC { let states = &mut *zlock!(statesref); let entry = states.timestamped_states.entry(ID::from(zid)); - let state = entry.or_insert(TimestampedState { - last_timestamp: None, + let state = entry.or_insert(SourceState:: { + last_delivered: None, pending_queries: 0, pending_samples: HashMap::new(), }); @@ -612,8 +605,8 @@ impl AdvancedSubscriber { let states = &mut *zlock!(statesref); let entry = states.sequenced_states.entry(source_id); let new = matches!(&entry, Entry::Vacant(_)); - let state = entry.or_insert(SequencedState { - last_seq_num: None, + let state = entry.or_insert(SourceState:: { + last_delivered: None, pending_queries: 0, pending_samples: HashMap::new(), }); @@ -749,7 +742,7 @@ impl Drop for InitialRepliesHandler { .collect::>(); pending_samples.sort_by_key(|(k, _s)| *k); for (seq_num, sample) in pending_samples { - state.last_seq_num = Some(seq_num); + state.last_delivered = Some(seq_num); self.callback.call(sample); } } @@ -768,7 +761,7 @@ impl Drop for InitialRepliesHandler { .collect::>(); pending_samples.sort_by_key(|(k, _s)| *k); for (timestamp, sample) in pending_samples { - state.last_timestamp = Some(timestamp); + state.last_delivered = Some(timestamp); self.callback.call(sample); } } @@ -802,7 +795,7 @@ impl Drop for SequencedRepliesHandler { .collect::>(); pending_samples.sort_by_key(|(k, _s)| *k); for (seq_num, sample) in pending_samples { - state.last_seq_num = Some(seq_num); + state.last_delivered = Some(seq_num); self.callback.call(sample); } } @@ -834,7 +827,7 @@ impl Drop for TimestampedRepliesHandler { .collect::>(); pending_samples.sort_by_key(|(k, _s)| *k); for (timestamp, sample) in pending_samples { - state.last_timestamp = Some(timestamp); + state.last_delivered = Some(timestamp); self.callback.call(sample); } } From 320170010e17f770027c50bf79e164997377e45e Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 14 Nov 2024 23:13:55 +0100 Subject: [PATCH 19/88] Code reorg --- zenoh-ext/src/advanced_subscriber.rs | 94 ++++++++++++---------------- 1 file changed, 41 insertions(+), 53 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 9936e85348..915a58b138 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -715,6 +715,41 @@ impl AdvancedSubscriber { } } +#[zenoh_macros::unstable] +#[inline] +fn flush_sequenced_source(state: &mut SourceState, callback: &Callback) { + if state.pending_queries == 0 && !state.pending_samples.is_empty() { + if state.last_delivered.is_some() { + tracing::error!("Sample missed: unable to retrieve some missing samples."); + } + let mut pending_samples = state + .pending_samples + .drain() + .collect::>(); + pending_samples.sort_by_key(|(k, _s)| *k); + for (seq_num, sample) in pending_samples { + state.last_delivered = Some(seq_num); + callback.call(sample); + } + } +} + +#[zenoh_macros::unstable] +#[inline] +fn flush_timestamped_source(state: &mut SourceState, callback: &Callback) { + if state.pending_queries == 0 && !state.pending_samples.is_empty() { + let mut pending_samples = state + .pending_samples + .drain() + .collect::>(); + pending_samples.sort_by_key(|(k, _s)| *k); + for (seq_num, sample) in pending_samples { + state.last_delivered = Some(seq_num); + callback.call(sample); + } + } +} + #[zenoh_macros::unstable] #[derive(Clone)] struct InitialRepliesHandler { @@ -731,21 +766,7 @@ impl Drop for InitialRepliesHandler { if states.global_pending_queries == 0 { for (source_id, state) in states.sequenced_states.iter_mut() { - if state.pending_queries == 0 - && !state.pending_samples.is_empty() - && states.global_pending_queries == 0 - { - tracing::error!("Sample missed: unable to retrieve some missing samples."); - let mut pending_samples = state - .pending_samples - .drain() - .collect::>(); - pending_samples.sort_by_key(|(k, _s)| *k); - for (seq_num, sample) in pending_samples { - state.last_delivered = Some(seq_num); - self.callback.call(sample); - } - } + flush_sequenced_source(state, &self.callback); if let Some((timer, period, query)) = self.periodic_query.as_ref() { timer.add(TimedEvent::periodic( *period, @@ -754,17 +775,7 @@ impl Drop for InitialRepliesHandler { } } for state in states.timestamped_states.values_mut() { - if state.pending_queries == 0 && !state.pending_samples.is_empty() { - let mut pending_samples = state - .pending_samples - .drain() - .collect::>(); - pending_samples.sort_by_key(|(k, _s)| *k); - for (timestamp, sample) in pending_samples { - state.last_delivered = Some(timestamp); - self.callback.call(sample); - } - } + flush_timestamped_source(state, &self.callback); } } } @@ -784,20 +795,8 @@ impl Drop for SequencedRepliesHandler { let states = &mut *zlock!(self.statesref); if let Some(state) = states.sequenced_states.get_mut(&self.source_id) { state.pending_queries = state.pending_queries.saturating_sub(1); - if state.pending_queries == 0 - && !state.pending_samples.is_empty() - && states.global_pending_queries == 0 - { - tracing::error!("Sample missed: unable to retrieve some missing samples."); - let mut pending_samples = state - .pending_samples - .drain() - .collect::>(); - pending_samples.sort_by_key(|(k, _s)| *k); - for (seq_num, sample) in pending_samples { - state.last_delivered = Some(seq_num); - self.callback.call(sample); - } + if states.global_pending_queries == 0 { + flush_sequenced_source(state, &self.callback) } } } @@ -817,19 +816,8 @@ impl Drop for TimestampedRepliesHandler { let states = &mut *zlock!(self.statesref); if let Some(state) = states.timestamped_states.get_mut(&self.id) { state.pending_queries = state.pending_queries.saturating_sub(1); - if state.pending_queries == 0 - && !state.pending_samples.is_empty() - && states.global_pending_queries == 0 - { - let mut pending_samples = state - .pending_samples - .drain() - .collect::>(); - pending_samples.sort_by_key(|(k, _s)| *k); - for (timestamp, sample) in pending_samples { - state.last_delivered = Some(timestamp); - self.callback.call(sample); - } + if states.global_pending_queries == 0 { + flush_timestamped_source(state, &self.callback); } } } From ded789cc8b0e6349163023580fca53139befc712 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 15 Nov 2024 00:35:58 +0100 Subject: [PATCH 20/88] Fix deduplication --- zenoh-ext/src/advanced_subscriber.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 915a58b138..dd715dd8e0 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -728,8 +728,14 @@ fn flush_sequenced_source(state: &mut SourceState, callback: &Callback>(); pending_samples.sort_by_key(|(k, _s)| *k); for (seq_num, sample) in pending_samples { - state.last_delivered = Some(seq_num); - callback.call(sample); + if state + .last_delivered + .map(|last| seq_num > last) + .unwrap_or(true) + { + state.last_delivered = Some(seq_num); + callback.call(sample); + } } } } @@ -743,9 +749,15 @@ fn flush_timestamped_source(state: &mut SourceState, callback: &Callb .drain() .collect::>(); pending_samples.sort_by_key(|(k, _s)| *k); - for (seq_num, sample) in pending_samples { - state.last_delivered = Some(seq_num); - callback.call(sample); + for (timestamp, sample) in pending_samples { + if state + .last_delivered + .map(|last| timestamp > last) + .unwrap_or(true) + { + state.last_delivered = Some(timestamp); + callback.call(sample); + } } } } From e03355377a3f07c8cfbc4284434f09dc94d7ec1e Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 15 Nov 2024 10:33:28 +0100 Subject: [PATCH 21/88] Subscribe to liveliness tokens with history --- zenoh-ext/src/advanced_subscriber.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index dd715dd8e0..9c78c8b973 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -566,6 +566,8 @@ impl AdvancedSubscriber { if s.kind() == SampleKind::Put { if let Ok(parsed) = ke_liveliness::parse(s.key_expr().as_keyexpr()) { if let Ok(zid) = ZenohId::from_str(parsed.zid().as_str()) { + // TODO : If we already have a state associated to this discovered source + // we should query with the appropriate range to avoid unnecessary retransmissions if parsed.eid() == KE_UHLC { let states = &mut *zlock!(statesref); let entry = states.timestamped_states.entry(ID::from(zid)); @@ -692,6 +694,7 @@ impl AdvancedSubscriber { .liveliness() .declare_subscriber(KE_PREFIX / KE_STAR / KE_STAR / &key_expr) // .declare_subscriber(keformat!(ke_liveliness_all::formatter(), zid = 0, eid = 0, remaining = key_expr).unwrap()) + .history(true) .callback(live_callback) .wait()?, ) From acaf341717ac7d5c17dc9ac23c5c26164894a827 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 15 Nov 2024 11:20:31 +0100 Subject: [PATCH 22/88] Update builders --- zenoh-ext/src/advanced_cache.rs | 56 +++++++++++++------ zenoh-ext/src/advanced_publisher.rs | 34 ++++-------- zenoh-ext/src/advanced_subscriber.rs | 82 +++++++++++++++------------- zenoh-ext/src/lib.rs | 4 +- zenoh-ext/src/publisher_ext.rs | 6 +- zenoh-ext/src/subscriber_ext.rs | 14 +++-- zenoh-ext/tests/advanced.rs | 20 ++++--- 7 files changed, 122 insertions(+), 94 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 410df20c44..af7751b43b 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -45,6 +45,38 @@ kedefine!( pub(crate) ke_liveliness: "@cache/${zid:*}/${eid:*}/${remaining:**}", ); +#[derive(Debug, Clone)] +/// Configure the history size of an [`AdvancedCache`]. +pub struct HistoryConf { + sample_depth: usize, + resources_limit: Option, +} + +impl Default for HistoryConf { + fn default() -> Self { + Self { + sample_depth: 1, + resources_limit: None, + } + } +} + +impl HistoryConf { + /// Specify how many samples to keep for each resource. + pub fn sample_depth(mut self, depth: usize) -> Self { + self.sample_depth = depth; + self + } + + // TODO pub fn time_depth(mut self, depth: Duration) -> Self + + /// Specify the maximum total number of samples to keep. + pub fn resources_limit(mut self, limit: usize) -> Self { + self.resources_limit = Some(limit); + self + } +} + /// The builder of AdvancedCache, allowing to configure it. pub struct AdvancedCacheBuilder<'a, 'b, 'c> { session: &'a Session, @@ -52,9 +84,8 @@ pub struct AdvancedCacheBuilder<'a, 'b, 'c> { queryable_prefix: Option>>, subscriber_origin: Locality, queryable_origin: Locality, - history: usize, + history: HistoryConf, liveliness: bool, - resources_limit: Option, } impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { @@ -68,9 +99,8 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { queryable_prefix: Some(Ok((KE_PREFIX / KE_STAR / KE_STAR).into())), subscriber_origin: Locality::default(), queryable_origin: Locality::default(), - history: 1024, + history: HistoryConf::default(), liveliness: false, - resources_limit: None, } } @@ -101,17 +131,11 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { } /// Change the history size for each resource. - pub fn history(mut self, history: usize) -> Self { + pub fn history(mut self, history: HistoryConf) -> Self { self.history = history; self } - /// Change the limit number of cached resources. - pub fn resources_limit(mut self, limit: usize) -> Self { - self.resources_limit = Some(limit); - self - } - pub fn liveliness(mut self, enabled: bool) -> Self { self.liveliness = enabled; self @@ -180,10 +204,9 @@ impl AdvancedCache { Some(Err(e)) => bail!("Invalid key expression for queryable_prefix: {}", e), }; tracing::debug!( - "Create AdvancedCache on {} with history={} resource_limit={:?}", + "Create AdvancedCache on {} with history={:?}", &key_expr, conf.history, - conf.resources_limit ); // declare the local subscriber that will store the local publications @@ -204,14 +227,13 @@ impl AdvancedCache { let sub_recv = sub.handler().clone(); let quer_recv = queryable.handler().clone(); let pub_key_expr = key_expr.into_owned(); - let resources_limit = conf.resources_limit; let history = conf.history; let (stoptx, stoprx) = bounded::(1); task::spawn(async move { let mut cache: HashMap> = - HashMap::with_capacity(resources_limit.unwrap_or(32)); - let limit = resources_limit.unwrap_or(usize::MAX); + HashMap::with_capacity(history.resources_limit.unwrap_or(32)); + let limit = history.resources_limit.unwrap_or(usize::MAX); loop { select!( @@ -225,7 +247,7 @@ impl AdvancedCache { }; if let Some(queue) = cache.get_mut(queryable_key_expr.as_keyexpr()) { - if queue.len() >= history { + if queue.len() >= history.sample_depth { queue.pop_front(); } queue.push_back(sample); diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 01c2264c3b..912b2679f2 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -29,7 +29,7 @@ use zenoh::{ }; use crate::{ - advanced_cache::{AdvancedCache, KE_PREFIX, KE_UHLC}, + advanced_cache::{AdvancedCache, HistoryConf, KE_PREFIX, KE_UHLC}, SessionExt, }; @@ -47,8 +47,7 @@ pub struct AdvancedPublisherBuilder<'a, 'b> { sequencing: Sequencing, liveliness: bool, cache: bool, - history: usize, - resources_limit: Option, + history: HistoryConf, } impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { @@ -62,8 +61,7 @@ impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { sequencing: Sequencing::None, liveliness: false, cache: false, - history: 1, - resources_limit: None, + history: HistoryConf::default(), } } @@ -77,19 +75,13 @@ impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { } /// Change the history size for each resource. - pub fn history(mut self, history: usize) -> Self { + pub fn history(mut self, history: HistoryConf) -> Self { self.cache = true; self.sequencing = Sequencing::Timestamp; self.history = history; self } - /// Change the limit number of cached resources. - pub fn resources_limit(mut self, limit: usize) -> Self { - self.resources_limit = Some(limit); - self - } - /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. @@ -159,16 +151,14 @@ impl<'a> AdvancedPublisher<'a> { }; let cache = if conf.cache { - let mut builder = conf - .session - .declare_advanced_cache(key_expr.clone().into_owned()) - .subscriber_allowed_origin(Locality::SessionLocal) - .history(conf.history) - .queryable_prefix(&prefix); - if let Some(resources_limit) = conf.resources_limit { - builder = builder.resources_limit(resources_limit); - } - Some(builder.wait()?) + Some( + conf.session + .declare_advanced_cache(key_expr.clone().into_owned()) + .subscriber_allowed_origin(Locality::SessionLocal) + .history(conf.history) + .queryable_prefix(&prefix) + .wait()?, + ) } else { None }; diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 9c78c8b973..ebb56e45e9 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -43,16 +43,38 @@ use { use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; +#[derive(Debug, Default, Clone)] +/// Configure the history size of an [`AdvancedCache`]. +pub struct RetransmissionConf { + periodic_queries: Option, +} + +impl RetransmissionConf { + /// Enable periodic queries for not yet received Samples and specify their period. + /// + /// This allows to retrieve the last Sample(s) if the last Sample(s) is/are lost. + /// So it is useful for sporadic publications but useless for periodic publications + /// with a period smaller or equal to this period. + /// Retransmission can only be achieved by Publishers that also activate retransmission. + #[zenoh_macros::unstable] + #[inline] + pub fn periodic_queries(mut self, period: Option) -> Self { + self.periodic_queries = period; + self + } + + // TODO pub fn sample_miss_callback(mut self, callback: Callback) -> Self +} + /// The builder of AdvancedSubscriber, allowing to configure it. #[zenoh_macros::unstable] pub struct AdvancedSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = false> { pub(crate) session: &'a Session, pub(crate) key_expr: ZResult>, pub(crate) origin: Locality, - pub(crate) retransmission: bool, + pub(crate) retransmission: Option, pub(crate) query_target: QueryTarget, pub(crate) query_timeout: Duration, - pub(crate) period: Option, pub(crate) history: bool, pub(crate) liveliness: bool, pub(crate) handler: Handler, @@ -71,12 +93,11 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { key_expr, origin, handler, - retransmission: false, + retransmission: None, query_target: QueryTarget::All, query_timeout: Duration::from_secs(10), history: false, liveliness: false, - period: None, } } } @@ -99,7 +120,6 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, - period: self.period, history: self.history, liveliness: self.liveliness, handler: callback, @@ -134,7 +154,6 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, - period: self.period, history: self.history, liveliness: self.liveliness, handler, @@ -158,8 +177,8 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { /// Retransmission can only be achieved by Publishers that also activate retransmission. #[zenoh_macros::unstable] #[inline] - pub fn retransmission(mut self) -> Self { - self.retransmission = true; + pub fn retransmission(mut self, conf: RetransmissionConf) -> Self { + self.retransmission = Some(conf); self } @@ -179,25 +198,13 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { self } - /// Enable periodic queries for not yet received Samples and specify their period. - /// - /// This allows to retrieve the last Sample(s) if the last Sample(s) is/are lost. - /// So it is useful for sporadic publications but useless for periodic publications - /// with a period smaller or equal to this period. - /// Retransmission can only be achieved by Publishers that also activate retransmission. - #[zenoh_macros::unstable] - #[inline] - pub fn periodic_queries(mut self, period: Option) -> Self { - self.period = period; - self - } - /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that also activate history. #[zenoh_macros::unstable] #[inline] pub fn history(mut self) -> Self { + // TODO take HistoryConf as parameter self.history = true; self } @@ -221,7 +228,6 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, - period: self.period, history: self.history, liveliness: self.liveliness, handler: self.handler, @@ -439,20 +445,22 @@ impl AdvancedSubscriber { let query_target = conf.query_target; let query_timeout = conf.query_timeout; let session = conf.session.clone(); - let periodic_query = conf.period.map(|period| { - ( - Arc::new(Timer::new(false)), - period, - PeriodicQuery { - source_id: None, - statesref: statesref.clone(), - key_expr: key_expr.clone().into_owned(), - session, - query_target, - query_timeout, - callback: callback.clone(), - }, - ) + let periodic_query = retransmission.as_ref().and_then(|r| { + r.periodic_queries.map(|period| { + ( + Arc::new(Timer::new(false)), + period, + PeriodicQuery { + source_id: None, + statesref: statesref.clone(), + key_expr: key_expr.clone().into_owned(), + session, + query_target, + query_timeout, + callback: callback.clone(), + }, + ) + }) }); let sub_callback = { @@ -479,7 +487,7 @@ impl AdvancedSubscriber { } if let Some(state) = states.sequenced_states.get_mut(&source_id) { - if retransmission + if retransmission.is_some() && state.pending_queries == 0 && !state.pending_samples.is_empty() { diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 3a1046fd87..7a64ba441e 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -39,9 +39,9 @@ pub use crate::serialization::{ }; #[cfg(feature = "unstable")] pub use crate::{ - advanced_cache::{AdvancedCache, AdvancedCacheBuilder}, + advanced_cache::{AdvancedCache, AdvancedCacheBuilder, HistoryConf}, advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder, Sequencing}, - advanced_subscriber::{AdvancedSubscriber, AdvancedSubscriberBuilder}, + advanced_subscriber::{AdvancedSubscriber, AdvancedSubscriberBuilder, RetransmissionConf}, publication_cache::{PublicationCache, PublicationCacheBuilder}, publisher_ext::PublisherBuilderExt, querying_subscriber::{ diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index b13cf29b68..2767c96dc2 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -13,7 +13,7 @@ // use zenoh::pubsub::PublisherBuilder; -use crate::AdvancedPublisherBuilder; +use crate::{advanced_cache::HistoryConf, AdvancedPublisherBuilder}; /// Some extensions to the [`zenoh::publication::PublisherBuilder`](zenoh::publication::PublisherBuilder) #[zenoh_macros::unstable] @@ -21,7 +21,7 @@ pub trait PublisherBuilderExt<'a, 'b> { /// Allow matching Subscribers to detect lost samples and ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. - fn history(self, history: usize) -> AdvancedPublisherBuilder<'a, 'b>; + fn history(self, history: HistoryConf) -> AdvancedPublisherBuilder<'a, 'b>; /// Allow this publisher to be detected by subscribers. /// @@ -33,7 +33,7 @@ impl<'a, 'b> PublisherBuilderExt<'a, 'b> for PublisherBuilder<'a, 'b> { /// Allow matching Subscribers to detect lost samples and ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. - fn history(self, history: usize) -> AdvancedPublisherBuilder<'a, 'b> { + fn history(self, history: HistoryConf) -> AdvancedPublisherBuilder<'a, 'b> { AdvancedPublisherBuilder::new(self.session, self.key_expr).history(history) } diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 893f23325c..e3af8309cf 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -25,7 +25,7 @@ use zenoh::{ use crate::{ querying_subscriber::QueryingSubscriberBuilder, AdvancedSubscriberBuilder, ExtractSample, - FetchingSubscriberBuilder, + FetchingSubscriberBuilder, RetransmissionConf, }; /// Allows writing `subscriber.forward(receiver)` instead of `subscriber.stream().map(Ok).forward(publisher)` @@ -129,12 +129,13 @@ pub trait DataSubscriberBuilderExt<'a, 'b, Handler> { /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that also activate history. - fn history(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + fn history(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; // TODO take HistoryConf as parameter /// Ask for retransmission of detected lost Samples. /// /// Retransmission can only be achieved by Publishers that also activate retransmission. - fn retransmission(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + fn retransmission(self, conf: RetransmissionConf) + -> AdvancedSubscriberBuilder<'a, 'b, Handler>; /// Enable detection of late joiner publishers and query for their historical data. /// @@ -261,9 +262,12 @@ impl<'a, 'b, Handler> DataSubscriberBuilderExt<'a, 'b, Handler> /// Ask for retransmission of detected lost Samples. /// /// Retransmission can only be achieved by Publishers that also activate retransmission. - fn retransmission(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + fn retransmission( + self, + conf: RetransmissionConf, + ) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .retransmission() + .retransmission(conf) } /// Enable detection of late joiner publishers and query for their historical data. diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index f099e2b265..b69cce3f73 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -14,7 +14,7 @@ use zenoh::sample::SampleKind; use zenoh_config::{EndPoint, ModeDependentValue, WhatAmI}; -use zenoh_ext::{DataSubscriberBuilderExt, PublisherBuilderExt}; +use zenoh_ext::{DataSubscriberBuilderExt, HistoryConf, PublisherBuilderExt, RetransmissionConf}; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_advanced_history() { @@ -46,7 +46,10 @@ async fn test_advanced_history() { s }; - let publ = ztimeout!(peer1.declare_publisher(ADVANCED_HISTORY_KEYEXPR).history(3)).unwrap(); + let publ = ztimeout!(peer1 + .declare_publisher(ADVANCED_HISTORY_KEYEXPR) + .history(HistoryConf::default().sample_depth(3))) + .unwrap(); ztimeout!(publ.put("1")).unwrap(); ztimeout!(publ.put("2")).unwrap(); ztimeout!(publ.put("3")).unwrap(); @@ -154,13 +157,13 @@ async fn test_advanced_retransmission() { let sub = ztimeout!(client2 .declare_subscriber(ADVANCED_RETRANSMISSION_KEYEXPR) - .retransmission()) + .retransmission(RetransmissionConf::default())) .unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(client1 .declare_publisher(ADVANCED_RETRANSMISSION_KEYEXPR) - .history(10) + .history(HistoryConf::default().sample_depth(10)) .retransmission()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); @@ -283,14 +286,15 @@ async fn test_advanced_retransmission_periodic() { let sub = ztimeout!(client2 .declare_subscriber(ADVANCED_RETRANSMISSION_PERIODIC_KEYEXPR) - .retransmission() - .periodic_queries(Some(Duration::from_secs(1)))) + .retransmission( + RetransmissionConf::default().periodic_queries(Some(Duration::from_secs(1))) + )) .unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(client1 .declare_publisher(ADVANCED_RETRANSMISSION_PERIODIC_KEYEXPR) - .history(10) + .history(HistoryConf::default().sample_depth(10)) .retransmission()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); @@ -403,7 +407,7 @@ async fn test_advanced_late_joiner() { let publ = ztimeout!(peer1 .declare_publisher(ADVANCED_LATE_JOINER_KEYEXPR) - .history(10) + .history(HistoryConf::default().sample_depth(10)) .late_joiner()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); From ef6316586f7e84dc5e4cf18230a0211b012c3682 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 15 Nov 2024 11:29:14 +0100 Subject: [PATCH 23/88] Add examples --- zenoh-ext/examples/examples/README.md | 27 +++++++ zenoh-ext/examples/examples/z_advanced_pub.rs | 73 +++++++++++++++++++ zenoh-ext/examples/examples/z_advanced_sub.rs | 69 ++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 zenoh-ext/examples/examples/z_advanced_pub.rs create mode 100644 zenoh-ext/examples/examples/z_advanced_sub.rs diff --git a/zenoh-ext/examples/examples/README.md b/zenoh-ext/examples/examples/README.md index 498a1ca6fe..f294d21ddb 100644 --- a/zenoh-ext/examples/examples/README.md +++ b/zenoh-ext/examples/examples/README.md @@ -15,6 +15,33 @@ ## Examples description +### z_advanced_pub + + Declares an AdvancedPublisher with a given key expression. + All the publications are locally cached (with a configurable history size - i.e. max number of cached data per resource, default 1). The cache can be queried by an AdvancedSubscriber for hsitory + or retransmission. + + Typical usage: + ```bash + z_advanced_pub + ``` + or + ```bash + z_advanced_pub --history 10 + ``` + +### z_advanced_sub + + Declares an AdvancedSubscriber with a given key expression. + The AdvancedSubscriber can query for AdvancedPublisher history at startup + and on late joiner publisher detection. The AdvancedSubscriber can detect + sample loss and ask for retransmission. + + Typical usage: + ```bash + z_advanced_sub + ``` + ### z_pub_cache Declares a publisher and an associated publication cache with a given key expression. diff --git a/zenoh-ext/examples/examples/z_advanced_pub.rs b/zenoh-ext/examples/examples/z_advanced_pub.rs new file mode 100644 index 0000000000..8d18ef36a0 --- /dev/null +++ b/zenoh-ext/examples/examples/z_advanced_pub.rs @@ -0,0 +1,73 @@ +use std::time::Duration; + +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use clap::{arg, Parser}; +use zenoh::{config::Config, key_expr::KeyExpr}; +use zenoh_config::ModeDependentValue; +use zenoh_ext::*; +use zenoh_ext_examples::CommonArgs; + +#[tokio::main] +async fn main() { + // Initiate logging + zenoh::init_log_from_env_or("error"); + + let (config, key_expr, value, history) = parse_args(); + + println!("Opening session..."); + let session = zenoh::open(config).await.unwrap(); + + println!("Declaring AdvancedPublisher on {}", &key_expr); + let publisher = session + .declare_publisher(&key_expr) + .history(HistoryConf::default().sample_depth(history)) + .retransmission() + .late_joiner() + .await + .unwrap(); + + println!("Press CTRL-C to quit..."); + for idx in 0..u32::MAX { + tokio::time::sleep(Duration::from_secs(1)).await; + let buf = format!("[{idx:4}] {value}"); + println!("Put Data ('{}': '{}')", &key_expr, buf); + publisher.put(buf).await.unwrap(); + } +} + +#[derive(clap::Parser, Clone, PartialEq, Eq, Hash, Debug)] +struct Args { + #[arg(short, long, default_value = "demo/example/zenoh-rs-pub")] + /// The key expression to publish. + key: KeyExpr<'static>, + #[arg(short, long, default_value = "Pub from Rust!")] + /// The value to reply to queries. + value: String, + #[arg(short = 'i', long, default_value = "1")] + /// The number of publications to keep in cache. + history: usize, + #[command(flatten)] + common: CommonArgs, +} + +fn parse_args() -> (Config, KeyExpr<'static>, String, usize) { + let args = Args::parse(); + let mut config: Config = args.common.into(); + config + .timestamping + .set_enabled(Some(ModeDependentValue::Unique(true))) + .unwrap(); + (config, args.key, args.value, args.history) +} diff --git a/zenoh-ext/examples/examples/z_advanced_sub.rs b/zenoh-ext/examples/examples/z_advanced_sub.rs new file mode 100644 index 0000000000..b9ad95435a --- /dev/null +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -0,0 +1,69 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use std::time::Duration; + +use clap::{arg, Parser}; +use zenoh::config::Config; +use zenoh_ext::*; +use zenoh_ext_examples::CommonArgs; + +#[tokio::main] +async fn main() { + // Initiate logging + zenoh::init_log_from_env_or("error"); + + let (config, key_expr) = parse_args(); + + println!("Opening session..."); + let session = zenoh::open(config).await.unwrap(); + + println!("Declaring AdvancedSubscriber on {}", key_expr,); + let subscriber = session + .declare_subscriber(key_expr) + .history() + .retransmission( + RetransmissionConf::default().periodic_queries(Some(Duration::from_secs(1))), + ) + .late_joiner() + .await + .unwrap(); + + println!("Press CTRL-C to quit..."); + while let Ok(sample) = subscriber.recv_async().await { + let payload = sample + .payload() + .try_to_string() + .unwrap_or_else(|e| e.to_string().into()); + println!( + ">> [Subscriber] Received {} ('{}': '{}')", + sample.kind(), + sample.key_expr().as_str(), + payload + ); + } +} + +#[derive(clap::Parser, Clone, PartialEq, Eq, Hash, Debug)] +struct Args { + #[arg(short, long, default_value = "demo/example/**")] + /// The key expression to subscribe onto. + key: String, + #[command(flatten)] + common: CommonArgs, +} + +fn parse_args() -> (Config, String) { + let args = Args::parse(); + (args.common.into(), args.key) +} From 788a8e51e1c764fa351413c0cecbbe6a87c96ef2 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 15 Nov 2024 12:31:05 +0100 Subject: [PATCH 24/88] Fix rustdoc --- zenoh-ext/src/advanced_subscriber.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index ebb56e45e9..36b1263e24 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -44,7 +44,7 @@ use { use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; #[derive(Debug, Default, Clone)] -/// Configure the history size of an [`AdvancedCache`]. +/// Configure retransmission. pub struct RetransmissionConf { periodic_queries: Option, } From a40639c7ba3399c4ca27ab11c5813b346600d142 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 18 Nov 2024 11:57:48 +0100 Subject: [PATCH 25/88] Move stuff in State --- zenoh-ext/src/advanced_subscriber.rs | 52 +++++++++++++--------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 36b1263e24..fb8d988719 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -271,6 +271,9 @@ struct State { global_pending_queries: u64, sequenced_states: HashMap>, timestamped_states: HashMap>, + query_target: QueryTarget, + query_timeout: Duration, + callback: Callback, } #[zenoh_macros::unstable] @@ -303,7 +306,7 @@ impl std::ops::DerefMut for AdvancedSubscriber { } #[zenoh_macros::unstable] -fn handle_sample(states: &mut State, sample: Sample, callback: &Callback) -> bool { +fn handle_sample(states: &mut State, sample: Sample) -> bool { if let (Some(source_id), Some(source_sn)) = ( sample.source_info().source_id(), sample.source_info().source_sn(), @@ -322,11 +325,11 @@ fn handle_sample(states: &mut State, sample: Sample, callback: &Callback state.pending_samples.insert(source_sn, sample); } } else { - callback.call(sample); + states.callback.call(sample); let mut last_seq_num = source_sn; state.last_delivered = Some(last_seq_num); while let Some(s) = state.pending_samples.remove(&(last_seq_num + 1)) { - callback.call(s); + states.callback.call(s); last_seq_num += 1; state.last_delivered = Some(last_seq_num); } @@ -342,14 +345,14 @@ fn handle_sample(states: &mut State, sample: Sample, callback: &Callback if state.last_delivered.map(|t| t < *timestamp).unwrap_or(true) { if states.global_pending_queries == 0 && state.pending_queries == 0 { state.last_delivered = Some(*timestamp); - callback.call(sample); + states.callback.call(sample); } else { state.pending_samples.entry(*timestamp).or_insert(sample); } } false } else { - callback.call(sample); + states.callback.call(sample); false } } @@ -371,9 +374,6 @@ struct PeriodicQuery { statesref: Arc>, key_expr: KeyExpr<'static>, session: Session, - query_target: QueryTarget, - query_timeout: Duration, - callback: Callback, } #[zenoh_macros::unstable] @@ -398,11 +398,13 @@ impl Timed for PeriodicQuery { / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() / &self.key_expr; let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); + + let query_target = states.query_target; + let query_timeout = states.query_timeout; drop(lock); let handler = SequencedRepliesHandler { source_id: *source_id, statesref: self.statesref.clone(), - callback: self.callback.clone(), }; let _ = self .session @@ -413,15 +415,15 @@ impl Timed for PeriodicQuery { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { let states = &mut *zlock!(handler.statesref); - handle_sample(states, s, &handler.callback); + handle_sample(states, s); } } } }) .consolidation(ConsolidationMode::None) .accept_replies(ReplyKeyExpr::Any) - .target(self.query_target) - .timeout(self.query_timeout) + .target(query_target) + .timeout(query_timeout) .wait(); } } @@ -434,12 +436,15 @@ impl AdvancedSubscriber { where H: IntoHandler + Send, { + let (callback, receiver) = conf.handler.into_handler(); let statesref = Arc::new(Mutex::new(State { sequenced_states: HashMap::new(), timestamped_states: HashMap::new(), global_pending_queries: if conf.history { 1 } else { 0 }, + query_target: conf.query_target, + query_timeout: conf.query_timeout, + callback: callback.clone(), })); - let (callback, receiver) = conf.handler.into_handler(); let key_expr = conf.key_expr?; let retransmission = conf.retransmission; let query_target = conf.query_target; @@ -455,9 +460,6 @@ impl AdvancedSubscriber { statesref: statesref.clone(), key_expr: key_expr.clone().into_owned(), session, - query_target, - query_timeout, - callback: callback.clone(), }, ) }) @@ -466,7 +468,6 @@ impl AdvancedSubscriber { let sub_callback = { let statesref = statesref.clone(); let session = conf.session.clone(); - let callback = callback.clone(); let key_expr = key_expr.clone().into_owned(); let periodic_query = periodic_query.clone(); @@ -474,7 +475,7 @@ impl AdvancedSubscriber { let mut lock = zlock!(statesref); let states = &mut *lock; let source_id = s.source_info().source_id().cloned(); - let new = handle_sample(states, s, &callback); + let new = handle_sample(states, s); if let Some(source_id) = source_id { if new { @@ -502,7 +503,6 @@ impl AdvancedSubscriber { let handler = SequencedRepliesHandler { source_id, statesref: statesref.clone(), - callback: callback.clone(), }; let _ = session .get(Selector::from((query_expr, seq_num_range))) @@ -512,7 +512,7 @@ impl AdvancedSubscriber { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { let states = &mut *zlock!(handler.statesref); - handle_sample(states, s, &handler.callback); + handle_sample(states, s); } } } @@ -553,7 +553,7 @@ impl AdvancedSubscriber { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { let states = &mut *zlock!(handler.statesref); - handle_sample(states, s, &handler.callback); + handle_sample(states, s); } } } @@ -600,7 +600,7 @@ impl AdvancedSubscriber { if key_expr.intersects(s.key_expr()) { let states = &mut *zlock!(handler.statesref); - handle_sample(states, s, &handler.callback); + handle_sample(states, s); } } } @@ -625,7 +625,6 @@ impl AdvancedSubscriber { let handler = SequencedRepliesHandler { source_id, statesref: statesref.clone(), - callback: callback.clone(), }; let _ = session .get(Selector::from((s.key_expr(), "0.."))) @@ -636,7 +635,7 @@ impl AdvancedSubscriber { if key_expr.intersects(s.key_expr()) { let states = &mut *zlock!(handler.statesref); - handle_sample(states, s, &handler.callback); + handle_sample(states, s); } } } @@ -675,7 +674,7 @@ impl AdvancedSubscriber { if let Ok(s) = r.into_result() { if key_expr.intersects(s.key_expr()) { let states = &mut *zlock!(handler.statesref); - handle_sample(states, s, &handler.callback); + handle_sample(states, s); } } } @@ -809,7 +808,6 @@ impl Drop for InitialRepliesHandler { struct SequencedRepliesHandler { source_id: EntityGlobalId, statesref: Arc>, - callback: Callback, } #[zenoh_macros::unstable] @@ -819,7 +817,7 @@ impl Drop for SequencedRepliesHandler { if let Some(state) = states.sequenced_states.get_mut(&self.source_id) { state.pending_queries = state.pending_queries.saturating_sub(1); if states.global_pending_queries == 0 { - flush_sequenced_source(state, &self.callback) + flush_sequenced_source(state, &states.callback) } } } From e1caa06d1555c7d9a2be2fc5e4e2934eb4bce3ed Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 18 Nov 2024 15:43:30 +0100 Subject: [PATCH 26/88] Code reorg --- zenoh-ext/src/advanced_subscriber.rs | 170 ++++++++++++--------------- 1 file changed, 77 insertions(+), 93 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index fb8d988719..7c8f5f3091 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -266,16 +266,38 @@ where } } +struct Period { + timer: Timer, + period: Duration, +} + #[zenoh_macros::unstable] struct State { global_pending_queries: u64, sequenced_states: HashMap>, timestamped_states: HashMap>, + session: Session, + key_expr: KeyExpr<'static>, + period: Option, query_target: QueryTarget, query_timeout: Duration, callback: Callback, } +macro_rules! spawn_periodoic_queries { + ($p:expr,$s:expr,$r:expr) => {{ + if let Some(period) = &$p.period { + period.timer.add(TimedEvent::periodic( + period.period, + PeriodicQuery { + source_id: $s, + statesref: $r, + }, + )) + } + }}; +} + #[zenoh_macros::unstable] struct SourceState { last_delivered: Option, @@ -370,18 +392,8 @@ fn seq_num_range(start: Option, end: Option) -> String { #[zenoh_macros::unstable] #[derive(Clone)] struct PeriodicQuery { - source_id: Option, + source_id: EntityGlobalId, statesref: Arc>, - key_expr: KeyExpr<'static>, - session: Session, -} - -#[zenoh_macros::unstable] -impl PeriodicQuery { - fn with_source_id(mut self, source_id: EntityGlobalId) -> Self { - self.source_id = Some(source_id); - self - } } #[zenoh_macros::unstable] @@ -390,42 +402,40 @@ impl Timed for PeriodicQuery { async fn run(&mut self) { let mut lock = zlock!(self.statesref); let states = &mut *lock; - if let Some(source_id) = &self.source_id { - if let Some(state) = states.sequenced_states.get_mut(source_id) { - state.pending_queries += 1; - let query_expr = KE_PREFIX - / &source_id.zid().into_keyexpr() - / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() - / &self.key_expr; - let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); - - let query_target = states.query_target; - let query_timeout = states.query_timeout; - drop(lock); - let handler = SequencedRepliesHandler { - source_id: *source_id, - statesref: self.statesref.clone(), - }; - let _ = self - .session - .get(Selector::from((query_expr, seq_num_range))) - .callback({ - let key_expr = self.key_expr.clone().into_owned(); - move |r: Reply| { - if let Ok(s) = r.into_result() { - if key_expr.intersects(s.key_expr()) { - let states = &mut *zlock!(handler.statesref); - handle_sample(states, s); - } + if let Some(state) = states.sequenced_states.get_mut(&self.source_id) { + state.pending_queries += 1; + let query_expr = KE_PREFIX + / &self.source_id.zid().into_keyexpr() + / &KeyExpr::try_from(self.source_id.eid().to_string()).unwrap() + / &states.key_expr; + let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); + + let session = states.session.clone(); + let key_expr = states.key_expr.clone().into_owned(); + let query_target = states.query_target; + let query_timeout = states.query_timeout; + drop(lock); + let handler = SequencedRepliesHandler { + source_id: self.source_id, + statesref: self.statesref.clone(), + }; + let _ = session + .get(Selector::from((query_expr, seq_num_range))) + .callback({ + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let states = &mut *zlock!(handler.statesref); + handle_sample(states, s); } } - }) - .consolidation(ConsolidationMode::None) - .accept_replies(ReplyKeyExpr::Any) - .target(query_target) - .timeout(query_timeout) - .wait(); - } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); } } } @@ -437,39 +447,32 @@ impl AdvancedSubscriber { H: IntoHandler + Send, { let (callback, receiver) = conf.handler.into_handler(); + let key_expr = conf.key_expr?; + let retransmission = conf.retransmission; + let query_target = conf.query_target; + let query_timeout = conf.query_timeout; + let session = conf.session.clone(); let statesref = Arc::new(Mutex::new(State { sequenced_states: HashMap::new(), timestamped_states: HashMap::new(), global_pending_queries: if conf.history { 1 } else { 0 }, + session, + period: retransmission.as_ref().and_then(|r| { + r.periodic_queries.map(|p| Period { + timer: Timer::new(false), + period: p, + }) + }), + key_expr: key_expr.clone().into_owned(), query_target: conf.query_target, query_timeout: conf.query_timeout, callback: callback.clone(), })); - let key_expr = conf.key_expr?; - let retransmission = conf.retransmission; - let query_target = conf.query_target; - let query_timeout = conf.query_timeout; - let session = conf.session.clone(); - let periodic_query = retransmission.as_ref().and_then(|r| { - r.periodic_queries.map(|period| { - ( - Arc::new(Timer::new(false)), - period, - PeriodicQuery { - source_id: None, - statesref: statesref.clone(), - key_expr: key_expr.clone().into_owned(), - session, - }, - ) - }) - }); let sub_callback = { let statesref = statesref.clone(); let session = conf.session.clone(); let key_expr = key_expr.clone().into_owned(); - let periodic_query = periodic_query.clone(); move |s: Sample| { let mut lock = zlock!(statesref); @@ -479,12 +482,7 @@ impl AdvancedSubscriber { if let Some(source_id) = source_id { if new { - if let Some((timer, period, query)) = periodic_query.as_ref() { - timer.add(TimedEvent::periodic( - *period, - query.clone().with_source_id(source_id), - )) - } + spawn_periodoic_queries!(states, source_id, statesref.clone()); } if let Some(state) = states.sequenced_states.get_mut(&source_id) { @@ -538,8 +536,6 @@ impl AdvancedSubscriber { if conf.history { let handler = InitialRepliesHandler { statesref: statesref.clone(), - callback: callback.clone(), - periodic_query: periodic_query.clone(), }; let _ = conf .session @@ -647,14 +643,11 @@ impl AdvancedSubscriber { .wait(); if new { - if let Some((timer, period, query)) = - periodic_query.as_ref() - { - timer.add(TimedEvent::periodic( - *period, - query.clone().with_source_id(source_id), - )) - } + spawn_periodoic_queries!( + states, + source_id, + statesref.clone() + ); } } } else { @@ -663,8 +656,6 @@ impl AdvancedSubscriber { let handler = InitialRepliesHandler { statesref: statesref.clone(), - periodic_query: None, - callback: callback.clone(), }; let _ = session .get(Selector::from((s.key_expr(), "0.."))) @@ -776,8 +767,6 @@ fn flush_timestamped_source(state: &mut SourceState, callback: &Callb #[derive(Clone)] struct InitialRepliesHandler { statesref: Arc>, - periodic_query: Option<(Arc, Duration, PeriodicQuery)>, - callback: Callback, } #[zenoh_macros::unstable] @@ -788,16 +777,11 @@ impl Drop for InitialRepliesHandler { if states.global_pending_queries == 0 { for (source_id, state) in states.sequenced_states.iter_mut() { - flush_sequenced_source(state, &self.callback); - if let Some((timer, period, query)) = self.periodic_query.as_ref() { - timer.add(TimedEvent::periodic( - *period, - query.clone().with_source_id(*source_id), - )) - } + flush_sequenced_source(state, &states.callback); + spawn_periodoic_queries!(states, *source_id, self.statesref.clone()); } for state in states.timestamped_states.values_mut() { - flush_timestamped_source(state, &self.callback); + flush_timestamped_source(state, &states.callback); } } } From ee1895f81fe8c76cc626a1013e925b740dbb16ac Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 18 Nov 2024 17:24:40 +0100 Subject: [PATCH 27/88] Add smaple_miss_callback --- zenoh-ext/src/advanced_subscriber.rs | 80 ++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 7c8f5f3091..b15d64b7d3 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -43,10 +43,19 @@ use { use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; -#[derive(Debug, Default, Clone)] +#[derive(Default)] /// Configure retransmission. pub struct RetransmissionConf { periodic_queries: Option, + sample_miss_callback: Option>, +} + +impl std::fmt::Debug for RetransmissionConf { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut s = f.debug_struct("RetransmissionConf"); + s.field("periodic_queries", &self.periodic_queries); + s.finish() + } } impl RetransmissionConf { @@ -63,7 +72,15 @@ impl RetransmissionConf { self } - // TODO pub fn sample_miss_callback(mut self, callback: Callback) -> Self + #[zenoh_macros::unstable] + #[inline] + pub fn sample_miss_callback( + mut self, + callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, + ) -> Self { + self.sample_miss_callback = Some(Arc::new(callback)); + self + } } /// The builder of AdvancedSubscriber, allowing to configure it. @@ -282,6 +299,7 @@ struct State { query_target: QueryTarget, query_timeout: Duration, callback: Callback, + miss_callback: Option>, } macro_rules! spawn_periodoic_queries { @@ -467,6 +485,9 @@ impl AdvancedSubscriber { query_target: conf.query_target, query_timeout: conf.query_timeout, callback: callback.clone(), + miss_callback: retransmission + .as_ref() + .and_then(|r| r.sample_miss_callback.clone()), })); let sub_callback = { @@ -718,24 +739,43 @@ impl AdvancedSubscriber { #[zenoh_macros::unstable] #[inline] -fn flush_sequenced_source(state: &mut SourceState, callback: &Callback) { +fn flush_sequenced_source( + state: &mut SourceState, + callback: &Callback, + source_id: &EntityGlobalId, + miss_callback: Option<&Arc>, +) { if state.pending_queries == 0 && !state.pending_samples.is_empty() { - if state.last_delivered.is_some() { - tracing::error!("Sample missed: unable to retrieve some missing samples."); - } let mut pending_samples = state .pending_samples .drain() .collect::>(); pending_samples.sort_by_key(|(k, _s)| *k); for (seq_num, sample) in pending_samples { - if state - .last_delivered - .map(|last| seq_num > last) - .unwrap_or(true) - { - state.last_delivered = Some(seq_num); - callback.call(sample); + match state.last_delivered { + None => { + state.last_delivered = Some(seq_num); + callback.call(sample); + } + Some(last) if seq_num == last + 1 => { + state.last_delivered = Some(seq_num); + callback.call(sample); + } + Some(last) if seq_num > last + 1 => { + tracing::warn!( + "Sample missed: missed {} samples from {:?}.", + seq_num - last - 1, + source_id, + ); + if let Some(miss_callback) = miss_callback { + (miss_callback)(*source_id, seq_num - last - 1) + } + state.last_delivered = Some(seq_num); + callback.call(sample); + } + _ => { + // duplicate + } } } } @@ -777,7 +817,12 @@ impl Drop for InitialRepliesHandler { if states.global_pending_queries == 0 { for (source_id, state) in states.sequenced_states.iter_mut() { - flush_sequenced_source(state, &states.callback); + flush_sequenced_source( + state, + &states.callback, + source_id, + states.miss_callback.as_ref(), + ); spawn_periodoic_queries!(states, *source_id, self.statesref.clone()); } for state in states.timestamped_states.values_mut() { @@ -801,7 +846,12 @@ impl Drop for SequencedRepliesHandler { if let Some(state) = states.sequenced_states.get_mut(&self.source_id) { state.pending_queries = state.pending_queries.saturating_sub(1); if states.global_pending_queries == 0 { - flush_sequenced_source(state, &states.callback) + flush_sequenced_source( + state, + &states.callback, + &self.source_id, + states.miss_callback.as_ref(), + ) } } } From c8a43d1194d65bc04375952eff0c9b25fa072551 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 18 Nov 2024 17:24:50 +0100 Subject: [PATCH 28/88] Add sample miss test --- zenoh-ext/tests/advanced.rs | 138 +++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index b69cce3f73..1ea9994938 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -354,6 +354,142 @@ async fn test_advanced_retransmission_periodic() { router.close().await.unwrap(); } +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_advanced_retransmission_sample_miss() { + use std::time::Duration; + + use zenoh::internal::ztimeout; + + const TIMEOUT: Duration = Duration::from_secs(60); + const SLEEP: Duration = Duration::from_secs(1); + const RECONNECT_SLEEP: Duration = Duration::from_secs(5); + const ROUTER_ENDPOINT: &str = "tcp/localhost:47453"; + + const ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR: &str = + "test/advanced/retransmission/sample_miss"; + + zenoh_util::init_log_from_env_or("error"); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + + let client1 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (1) ZID: {}", s.zid()); + s + }; + + let client2 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (2) ZID: {}", s.zid()); + s + }; + + let (miss_sender, miss_receiver) = flume::unbounded(); + + let sub = ztimeout!(client2 + .declare_subscriber(ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR) + .retransmission( + RetransmissionConf::default() + .periodic_queries(Some(Duration::from_secs(1))) + .sample_miss_callback(move |s, m| { + miss_sender.send((s, m)).unwrap(); + }) + )) + .unwrap(); + tokio::time::sleep(SLEEP).await; + + let publ = ztimeout!(client1 + .declare_publisher(ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR) + .history(HistoryConf::default().sample_depth(1)) + .retransmission()) + .unwrap(); + ztimeout!(publ.put("1")).unwrap(); + + tokio::time::sleep(SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "1"); + + assert!(sub.try_recv().unwrap().is_none()); + + router.close().await.unwrap(); + tokio::time::sleep(SLEEP).await; + + ztimeout!(publ.put("2")).unwrap(); + ztimeout!(publ.put("3")).unwrap(); + ztimeout!(publ.put("4")).unwrap(); + tokio::time::sleep(SLEEP).await; + + assert!(sub.try_recv().unwrap().is_none()); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + tokio::time::sleep(RECONNECT_SLEEP).await; + + ztimeout!(publ.put("5")).unwrap(); + tokio::time::sleep(SLEEP).await; + + let miss = ztimeout!(miss_receiver.recv_async()).unwrap(); + assert_eq!(miss.0, publ.id()); + assert_eq!(miss.1, 2); + + assert!(miss_receiver.try_recv().is_err()); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "4"); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "5"); + + assert!(sub.try_recv().unwrap().is_none()); + + publ.undeclare().await.unwrap(); + // sub.undeclare().await.unwrap(); + + client1.close().await.unwrap(); + client2.close().await.unwrap(); + + router.close().await.unwrap(); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_advanced_late_joiner() { use std::time::Duration; @@ -363,7 +499,7 @@ async fn test_advanced_late_joiner() { const TIMEOUT: Duration = Duration::from_secs(60); const SLEEP: Duration = Duration::from_secs(1); const RECONNECT_SLEEP: Duration = Duration::from_secs(8); - const ROUTER_ENDPOINT: &str = "tcp/localhost:47453"; + const ROUTER_ENDPOINT: &str = "tcp/localhost:47454"; const ADVANCED_LATE_JOINER_KEYEXPR: &str = "test/advanced/late_joiner"; From d11243d1fe8c3c064d6e6846f38b14ef3c14bd89 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 18 Nov 2024 17:25:09 +0100 Subject: [PATCH 29/88] Update z_advanced_sub example --- zenoh-ext/examples/examples/z_advanced_sub.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/examples/examples/z_advanced_sub.rs b/zenoh-ext/examples/examples/z_advanced_sub.rs index b9ad95435a..a6139a0cd1 100644 --- a/zenoh-ext/examples/examples/z_advanced_sub.rs +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -33,7 +33,11 @@ async fn main() { .declare_subscriber(key_expr) .history() .retransmission( - RetransmissionConf::default().periodic_queries(Some(Duration::from_secs(1))), + RetransmissionConf::default() + .periodic_queries(Some(Duration::from_secs(1))) + .sample_miss_callback(|s, m| { + println!(">> [Subscriber] Missed {} samples from {:?} !!!", m, s); + }), ) .late_joiner() .await From eb6dbf67db0654dc1fce6eafb520842998e62598 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 21 Nov 2024 17:31:25 +0100 Subject: [PATCH 30/88] Explicit use in examples --- zenoh-ext/examples/examples/z_advanced_pub.rs | 2 +- zenoh-ext/examples/examples/z_advanced_sub.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/examples/examples/z_advanced_pub.rs b/zenoh-ext/examples/examples/z_advanced_pub.rs index 8d18ef36a0..4b629f0ac9 100644 --- a/zenoh-ext/examples/examples/z_advanced_pub.rs +++ b/zenoh-ext/examples/examples/z_advanced_pub.rs @@ -16,7 +16,7 @@ use std::time::Duration; use clap::{arg, Parser}; use zenoh::{config::Config, key_expr::KeyExpr}; use zenoh_config::ModeDependentValue; -use zenoh_ext::*; +use zenoh_ext::{HistoryConf, PublisherBuilderExt}; use zenoh_ext_examples::CommonArgs; #[tokio::main] diff --git a/zenoh-ext/examples/examples/z_advanced_sub.rs b/zenoh-ext/examples/examples/z_advanced_sub.rs index a6139a0cd1..5d7b1d02cd 100644 --- a/zenoh-ext/examples/examples/z_advanced_sub.rs +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -15,7 +15,7 @@ use std::time::Duration; use clap::{arg, Parser}; use zenoh::config::Config; -use zenoh_ext::*; +use zenoh_ext::{DataSubscriberBuilderExt, RetransmissionConf}; use zenoh_ext_examples::CommonArgs; #[tokio::main] From ee93de12656af309f65f6abdbc0256d862d03437 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 11:25:54 +0100 Subject: [PATCH 31/88] Update API --- zenoh-ext/examples/examples/z_advanced_pub.rs | 8 +- zenoh-ext/examples/examples/z_advanced_sub.rs | 16 +-- zenoh-ext/src/advanced_cache.rs | 20 ++-- zenoh-ext/src/advanced_publisher.rs | 16 +-- zenoh-ext/src/advanced_subscriber.rs | 104 ++++++++++-------- zenoh-ext/src/lib.rs | 6 +- zenoh-ext/src/publisher_ext.rs | 20 ++-- zenoh-ext/src/subscriber_ext.rs | 45 +++----- zenoh-ext/tests/advanced.rs | 47 ++++---- 9 files changed, 139 insertions(+), 143 deletions(-) diff --git a/zenoh-ext/examples/examples/z_advanced_pub.rs b/zenoh-ext/examples/examples/z_advanced_pub.rs index 4b629f0ac9..cd6f5eac08 100644 --- a/zenoh-ext/examples/examples/z_advanced_pub.rs +++ b/zenoh-ext/examples/examples/z_advanced_pub.rs @@ -16,7 +16,7 @@ use std::time::Duration; use clap::{arg, Parser}; use zenoh::{config::Config, key_expr::KeyExpr}; use zenoh_config::ModeDependentValue; -use zenoh_ext::{HistoryConf, PublisherBuilderExt}; +use zenoh_ext::{CacheConfig, PublisherBuilderExt}; use zenoh_ext_examples::CommonArgs; #[tokio::main] @@ -32,9 +32,9 @@ async fn main() { println!("Declaring AdvancedPublisher on {}", &key_expr); let publisher = session .declare_publisher(&key_expr) - .history(HistoryConf::default().sample_depth(history)) - .retransmission() - .late_joiner() + .cache(CacheConfig::default().max_samples(history)) + .sample_miss_detection() + .late_joiner_detection() .await .unwrap(); diff --git a/zenoh-ext/examples/examples/z_advanced_sub.rs b/zenoh-ext/examples/examples/z_advanced_sub.rs index 5d7b1d02cd..f4167549f1 100644 --- a/zenoh-ext/examples/examples/z_advanced_sub.rs +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -15,7 +15,7 @@ use std::time::Duration; use clap::{arg, Parser}; use zenoh::config::Config; -use zenoh_ext::{DataSubscriberBuilderExt, RetransmissionConf}; +use zenoh_ext::{DataSubscriberBuilderExt, HistoryConfig, RecoveryConfig}; use zenoh_ext_examples::CommonArgs; #[tokio::main] @@ -31,15 +31,11 @@ async fn main() { println!("Declaring AdvancedSubscriber on {}", key_expr,); let subscriber = session .declare_subscriber(key_expr) - .history() - .retransmission( - RetransmissionConf::default() - .periodic_queries(Some(Duration::from_secs(1))) - .sample_miss_callback(|s, m| { - println!(">> [Subscriber] Missed {} samples from {:?} !!!", m, s); - }), - ) - .late_joiner() + .history(HistoryConfig::default().late_joiner()) + .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1)))) + .sample_miss_callback(|s, m| { + println!(">> [Subscriber] Missed {} samples from {:?} !!!", m, s); + }) .await .unwrap(); diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index af7751b43b..85b26264ae 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -46,13 +46,13 @@ kedefine!( ); #[derive(Debug, Clone)] -/// Configure the history size of an [`AdvancedCache`]. -pub struct HistoryConf { +/// Configure an [`AdvancedCache`]. +pub struct CacheConfig { sample_depth: usize, resources_limit: Option, } -impl Default for HistoryConf { +impl Default for CacheConfig { fn default() -> Self { Self { sample_depth: 1, @@ -61,17 +61,17 @@ impl Default for HistoryConf { } } -impl HistoryConf { +impl CacheConfig { /// Specify how many samples to keep for each resource. - pub fn sample_depth(mut self, depth: usize) -> Self { + pub fn max_samples(mut self, depth: usize) -> Self { self.sample_depth = depth; self } - // TODO pub fn time_depth(mut self, depth: Duration) -> Self + // TODO pub fn max_age(mut self, depth: Duration) -> Self /// Specify the maximum total number of samples to keep. - pub fn resources_limit(mut self, limit: usize) -> Self { + pub fn max_total_samples(mut self, limit: usize) -> Self { self.resources_limit = Some(limit); self } @@ -84,7 +84,7 @@ pub struct AdvancedCacheBuilder<'a, 'b, 'c> { queryable_prefix: Option>>, subscriber_origin: Locality, queryable_origin: Locality, - history: HistoryConf, + history: CacheConfig, liveliness: bool, } @@ -99,7 +99,7 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { queryable_prefix: Some(Ok((KE_PREFIX / KE_STAR / KE_STAR).into())), subscriber_origin: Locality::default(), queryable_origin: Locality::default(), - history: HistoryConf::default(), + history: CacheConfig::default(), liveliness: false, } } @@ -131,7 +131,7 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { } /// Change the history size for each resource. - pub fn history(mut self, history: HistoryConf) -> Self { + pub fn history(mut self, history: CacheConfig) -> Self { self.history = history; self } diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 912b2679f2..146ade4fc1 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -29,7 +29,7 @@ use zenoh::{ }; use crate::{ - advanced_cache::{AdvancedCache, HistoryConf, KE_PREFIX, KE_UHLC}, + advanced_cache::{AdvancedCache, CacheConfig, KE_PREFIX, KE_UHLC}, SessionExt, }; @@ -47,7 +47,7 @@ pub struct AdvancedPublisherBuilder<'a, 'b> { sequencing: Sequencing, liveliness: bool, cache: bool, - history: HistoryConf, + history: CacheConfig, } impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { @@ -61,31 +61,31 @@ impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { sequencing: Sequencing::None, liveliness: false, cache: false, - history: HistoryConf::default(), + history: CacheConfig::default(), } } - /// Allow matching Subscribers to detect lost samples and ask for retransimission. + /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. - pub fn retransmission(mut self) -> Self { + pub fn sample_miss_detection(mut self) -> Self { self.cache = true; self.sequencing = Sequencing::SequenceNumber; self } /// Change the history size for each resource. - pub fn history(mut self, history: HistoryConf) -> Self { + pub fn cache(mut self, config: CacheConfig) -> Self { self.cache = true; self.sequencing = Sequencing::Timestamp; - self.history = history; + self.history = config; self } /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - pub fn late_joiner(mut self) -> Self { + pub fn late_joiner_detection(mut self) -> Self { self.liveliness = true; self } diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index b15d64b7d3..eb76ca3708 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -43,14 +43,41 @@ use { use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; +#[derive(Debug, Default, Clone)] +/// Configure the history size of an [`AdvancedCache`]. +pub struct HistoryConfig { + liveliness: bool, + // sample_depth: usize, +} + +impl HistoryConfig { + /// Enable detection of late joiner publishers and query for their historical data. + /// + /// Let joiner detection can only be achieved for Publishers that enable late_joiner_detection. + /// History can only be retransmitted by Publishers that enable caching. + #[zenoh_macros::unstable] + #[inline] + pub fn late_joiner(mut self) -> Self { + self.liveliness = true; + self + } + + // /// Specify how many samples to keep for each resource. + // pub fn max_samples(mut self, depth: usize) -> Self { + // self.sample_depth = depth; + // self + // } + + // TODO pub fn max_age(mut self, depth: Duration) -> Self +} + #[derive(Default)] /// Configure retransmission. -pub struct RetransmissionConf { +pub struct RecoveryConfig { periodic_queries: Option, - sample_miss_callback: Option>, } -impl std::fmt::Debug for RetransmissionConf { +impl std::fmt::Debug for RecoveryConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("RetransmissionConf"); s.field("periodic_queries", &self.periodic_queries); @@ -58,7 +85,7 @@ impl std::fmt::Debug for RetransmissionConf { } } -impl RetransmissionConf { +impl RecoveryConfig { /// Enable periodic queries for not yet received Samples and specify their period. /// /// This allows to retrieve the last Sample(s) if the last Sample(s) is/are lost. @@ -71,16 +98,6 @@ impl RetransmissionConf { self.periodic_queries = period; self } - - #[zenoh_macros::unstable] - #[inline] - pub fn sample_miss_callback( - mut self, - callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, - ) -> Self { - self.sample_miss_callback = Some(Arc::new(callback)); - self - } } /// The builder of AdvancedSubscriber, allowing to configure it. @@ -89,11 +106,11 @@ pub struct AdvancedSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = f pub(crate) session: &'a Session, pub(crate) key_expr: ZResult>, pub(crate) origin: Locality, - pub(crate) retransmission: Option, + pub(crate) sample_miss_callback: Option>, + pub(crate) retransmission: Option, pub(crate) query_target: QueryTarget, pub(crate) query_timeout: Duration, - pub(crate) history: bool, - pub(crate) liveliness: bool, + pub(crate) history: Option, pub(crate) handler: Handler, } @@ -110,11 +127,11 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { key_expr, origin, handler, + sample_miss_callback: None, retransmission: None, query_target: QueryTarget::All, query_timeout: Duration::from_secs(10), - history: false, - liveliness: false, + history: None, } } } @@ -134,11 +151,11 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { session: self.session, key_expr: self.key_expr.map(|s| s.into_owned()), origin: self.origin, + sample_miss_callback: self.sample_miss_callback, retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, history: self.history, - liveliness: self.liveliness, handler: callback, } } @@ -168,11 +185,11 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { session: self.session, key_expr: self.key_expr.map(|s| s.into_owned()), origin: self.origin, + sample_miss_callback: self.sample_miss_callback, retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, history: self.history, - liveliness: self.liveliness, handler, } } @@ -189,12 +206,23 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { self } + #[zenoh_macros::unstable] + #[inline] + pub fn sample_miss_callback( + mut self, + callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, + ) -> Self { + self.sample_miss_callback = Some(Arc::new(callback)); + self + } + /// Ask for retransmission of detected lost Samples. /// - /// Retransmission can only be achieved by Publishers that also activate retransmission. + /// Retransmission can only be achieved by Publishers that enable + /// caching and sample_miss_detection. #[zenoh_macros::unstable] #[inline] - pub fn retransmission(mut self, conf: RetransmissionConf) -> Self { + pub fn recovery(mut self, conf: RecoveryConfig) -> Self { self.retransmission = Some(conf); self } @@ -217,23 +245,11 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { /// Enable query for historical data. /// - /// History can only be retransmitted by Publishers that also activate history. + /// History can only be retransmitted by Publishers that enable caching. #[zenoh_macros::unstable] #[inline] - pub fn history(mut self) -> Self { - // TODO take HistoryConf as parameter - self.history = true; - self - } - - /// Enable detection of late joiner publishers and query for their historical data. - /// - /// Let joiner detectiopn can only be achieved for Publishers that also activate late_joiner. - /// History can only be retransmitted by Publishers that also activate history. - #[zenoh_macros::unstable] - #[inline] - pub fn late_joiner(mut self) -> Self { - self.liveliness = true; + pub fn history(mut self, config: HistoryConfig) -> Self { + self.history = Some(config); self } @@ -242,11 +258,11 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { session: self.session, key_expr: self.key_expr.map(|s| s.into_owned()), origin: self.origin, + sample_miss_callback: self.sample_miss_callback, retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, history: self.history, - liveliness: self.liveliness, handler: self.handler, } } @@ -473,7 +489,7 @@ impl AdvancedSubscriber { let statesref = Arc::new(Mutex::new(State { sequenced_states: HashMap::new(), timestamped_states: HashMap::new(), - global_pending_queries: if conf.history { 1 } else { 0 }, + global_pending_queries: if conf.history.is_some() { 1 } else { 0 }, session, period: retransmission.as_ref().and_then(|r| { r.periodic_queries.map(|p| Period { @@ -485,9 +501,7 @@ impl AdvancedSubscriber { query_target: conf.query_target, query_timeout: conf.query_timeout, callback: callback.clone(), - miss_callback: retransmission - .as_ref() - .and_then(|r| r.sample_miss_callback.clone()), + miss_callback: conf.sample_miss_callback, })); let sub_callback = { @@ -554,7 +568,7 @@ impl AdvancedSubscriber { .allowed_origin(conf.origin) .wait()?; - if conf.history { + if conf.history.is_some() { let handler = InitialRepliesHandler { statesref: statesref.clone(), }; @@ -582,7 +596,7 @@ impl AdvancedSubscriber { .wait(); } - let liveliness_subscriber = if conf.history && conf.liveliness { + let liveliness_subscriber = if conf.history.is_some_and(|h| h.liveliness) { let live_callback = { let session = conf.session.clone(); let statesref = statesref.clone(); diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 7a64ba441e..c7e8701e30 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -39,9 +39,11 @@ pub use crate::serialization::{ }; #[cfg(feature = "unstable")] pub use crate::{ - advanced_cache::{AdvancedCache, AdvancedCacheBuilder, HistoryConf}, + advanced_cache::{AdvancedCache, AdvancedCacheBuilder, CacheConfig}, advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder, Sequencing}, - advanced_subscriber::{AdvancedSubscriber, AdvancedSubscriberBuilder, RetransmissionConf}, + advanced_subscriber::{ + AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, RecoveryConfig, + }, publication_cache::{PublicationCache, PublicationCacheBuilder}, publisher_ext::PublisherBuilderExt, querying_subscriber::{ diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index 2767c96dc2..a0e47818e4 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -13,34 +13,36 @@ // use zenoh::pubsub::PublisherBuilder; -use crate::{advanced_cache::HistoryConf, AdvancedPublisherBuilder}; +use crate::{advanced_cache::CacheConfig, AdvancedPublisherBuilder}; /// Some extensions to the [`zenoh::publication::PublisherBuilder`](zenoh::publication::PublisherBuilder) #[zenoh_macros::unstable] pub trait PublisherBuilderExt<'a, 'b> { - /// Allow matching Subscribers to detect lost samples and ask for retransimission. + /// Allow matching Subscribers to detect lost samples and + /// optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. - fn history(self, history: HistoryConf) -> AdvancedPublisherBuilder<'a, 'b>; + fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b>; /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - fn late_joiner(self) -> AdvancedPublisherBuilder<'a, 'b>; + fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b>; } impl<'a, 'b> PublisherBuilderExt<'a, 'b> for PublisherBuilder<'a, 'b> { - /// Allow matching Subscribers to detect lost samples and ask for retransimission. + /// Allow matching Subscribers to detect lost samples and + /// optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. - fn history(self, history: HistoryConf) -> AdvancedPublisherBuilder<'a, 'b> { - AdvancedPublisherBuilder::new(self.session, self.key_expr).history(history) + fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b> { + AdvancedPublisherBuilder::new(self.session, self.key_expr).cache(config) } /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - fn late_joiner(self) -> AdvancedPublisherBuilder<'a, 'b> { - AdvancedPublisherBuilder::new(self.session, self.key_expr).late_joiner() + fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b> { + AdvancedPublisherBuilder::new(self.session, self.key_expr).late_joiner_detection() } } diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index e3af8309cf..6742517edb 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -24,8 +24,8 @@ use zenoh::{ }; use crate::{ - querying_subscriber::QueryingSubscriberBuilder, AdvancedSubscriberBuilder, ExtractSample, - FetchingSubscriberBuilder, RetransmissionConf, + advanced_subscriber::HistoryConfig, querying_subscriber::QueryingSubscriberBuilder, + AdvancedSubscriberBuilder, ExtractSample, FetchingSubscriberBuilder, RecoveryConfig, }; /// Allows writing `subscriber.forward(receiver)` instead of `subscriber.stream().map(Ok).forward(publisher)` @@ -128,20 +128,14 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { pub trait DataSubscriberBuilderExt<'a, 'b, Handler> { /// Enable query for historical data. /// - /// History can only be retransmitted by Publishers that also activate history. - fn history(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; // TODO take HistoryConf as parameter + /// History can only be retransmitted by Publishers that enable caching. + fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; // TODO take HistoryConf as parameter /// Ask for retransmission of detected lost Samples. /// - /// Retransmission can only be achieved by Publishers that also activate retransmission. - fn retransmission(self, conf: RetransmissionConf) - -> AdvancedSubscriberBuilder<'a, 'b, Handler>; - - /// Enable detection of late joiner publishers and query for their historical data. - /// - /// Let joiner detectiopn can only be achieved for Publishers that also activate late_joiner. - /// History can only be retransmitted by Publishers that also activate history. - fn late_joiner(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + /// Retransmission can only be achieved by Publishers that enable + /// caching and sample_miss_detection. + fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; } impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilder<'a, 'b, Handler> { @@ -253,30 +247,19 @@ impl<'a, 'b, Handler> DataSubscriberBuilderExt<'a, 'b, Handler> { /// Enable query for historical data. /// - /// History can only be retransmitted by Publishers that also activate history. - fn history(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + /// History can only be retransmitted by Publishers that enable caching. + fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .history() + .history(config) } /// Ask for retransmission of detected lost Samples. /// - /// Retransmission can only be achieved by Publishers that also activate retransmission. - fn retransmission( - self, - conf: RetransmissionConf, - ) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { - AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .retransmission(conf) - } - - /// Enable detection of late joiner publishers and query for their historical data. - /// - /// Let joiner detectiopn can only be achieved for Publishers that also activate late_joiner. - /// History can only be retransmitted by Publishers that also activate history. - fn late_joiner(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + /// Retransmission can only be achieved by Publishers that enable + /// caching and sample_miss_detection. + fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .late_joiner() + .recovery(conf) } } diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index 1ea9994938..e706491a29 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -14,7 +14,9 @@ use zenoh::sample::SampleKind; use zenoh_config::{EndPoint, ModeDependentValue, WhatAmI}; -use zenoh_ext::{DataSubscriberBuilderExt, HistoryConf, PublisherBuilderExt, RetransmissionConf}; +use zenoh_ext::{ + CacheConfig, DataSubscriberBuilderExt, HistoryConfig, PublisherBuilderExt, RecoveryConfig, +}; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_advanced_history() { @@ -48,7 +50,7 @@ async fn test_advanced_history() { let publ = ztimeout!(peer1 .declare_publisher(ADVANCED_HISTORY_KEYEXPR) - .history(HistoryConf::default().sample_depth(3))) + .cache(CacheConfig::default().max_samples(3))) .unwrap(); ztimeout!(publ.put("1")).unwrap(); ztimeout!(publ.put("2")).unwrap(); @@ -70,7 +72,10 @@ async fn test_advanced_history() { s }; - let sub = ztimeout!(peer2.declare_subscriber(ADVANCED_HISTORY_KEYEXPR).history()).unwrap(); + let sub = ztimeout!(peer2 + .declare_subscriber(ADVANCED_HISTORY_KEYEXPR) + .history(HistoryConfig::default())) + .unwrap(); tokio::time::sleep(SLEEP).await; ztimeout!(publ.put("5")).unwrap(); @@ -157,14 +162,14 @@ async fn test_advanced_retransmission() { let sub = ztimeout!(client2 .declare_subscriber(ADVANCED_RETRANSMISSION_KEYEXPR) - .retransmission(RetransmissionConf::default())) + .recovery(RecoveryConfig::default())) .unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(client1 .declare_publisher(ADVANCED_RETRANSMISSION_KEYEXPR) - .history(HistoryConf::default().sample_depth(10)) - .retransmission()) + .cache(CacheConfig::default().max_samples(10)) + .sample_miss_detection()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); @@ -286,16 +291,14 @@ async fn test_advanced_retransmission_periodic() { let sub = ztimeout!(client2 .declare_subscriber(ADVANCED_RETRANSMISSION_PERIODIC_KEYEXPR) - .retransmission( - RetransmissionConf::default().periodic_queries(Some(Duration::from_secs(1))) - )) + .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1))))) .unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(client1 .declare_publisher(ADVANCED_RETRANSMISSION_PERIODIC_KEYEXPR) - .history(HistoryConf::default().sample_depth(10)) - .retransmission()) + .cache(CacheConfig::default().max_samples(10)) + .sample_miss_detection()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); @@ -413,20 +416,17 @@ async fn test_advanced_retransmission_sample_miss() { let sub = ztimeout!(client2 .declare_subscriber(ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR) - .retransmission( - RetransmissionConf::default() - .periodic_queries(Some(Duration::from_secs(1))) - .sample_miss_callback(move |s, m| { - miss_sender.send((s, m)).unwrap(); - }) - )) + .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1)))) + .sample_miss_callback(move |s, m| { + miss_sender.send((s, m)).unwrap(); + })) .unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(client1 .declare_publisher(ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR) - .history(HistoryConf::default().sample_depth(1)) - .retransmission()) + .cache(CacheConfig::default().max_samples(1)) + .sample_miss_detection()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); @@ -536,15 +536,14 @@ async fn test_advanced_late_joiner() { let sub = ztimeout!(peer2 .declare_subscriber(ADVANCED_LATE_JOINER_KEYEXPR) - .history() - .late_joiner()) + .history(HistoryConfig::default().late_joiner())) .unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(peer1 .declare_publisher(ADVANCED_LATE_JOINER_KEYEXPR) - .history(HistoryConf::default().sample_depth(10)) - .late_joiner()) + .cache(CacheConfig::default().max_samples(10)) + .late_joiner_detection()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); ztimeout!(publ.put("2")).unwrap(); From 596d0ed824222f283799334313a4869fe4ae7fbc Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 11:47:53 +0100 Subject: [PATCH 32/88] Fix rustdoc --- zenoh-ext/src/advanced_subscriber.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index eb76ca3708..7a50eca3b9 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -44,7 +44,7 @@ use { use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; #[derive(Debug, Default, Clone)] -/// Configure the history size of an [`AdvancedCache`]. +/// Configure query for historical data. pub struct HistoryConfig { liveliness: bool, // sample_depth: usize, From 35ae472bd0806d2cf215ae47b3668741b8407c4e Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 14:04:15 +0100 Subject: [PATCH 33/88] Allow sample miss detection when recovery disabled --- zenoh-ext/src/advanced_subscriber.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 7a50eca3b9..83a9136ed8 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -311,6 +311,7 @@ struct State { timestamped_states: HashMap>, session: Session, key_expr: KeyExpr<'static>, + retransmission: bool, period: Option, query_target: QueryTarget, query_timeout: Duration, @@ -378,7 +379,20 @@ fn handle_sample(states: &mut State, sample: Sample) -> bool { state.pending_samples.insert(source_sn, sample); } else if state.last_delivered.is_some() && source_sn != state.last_delivered.unwrap() + 1 { if source_sn > state.last_delivered.unwrap() { - state.pending_samples.insert(source_sn, sample); + if states.retransmission { + state.pending_samples.insert(source_sn, sample); + } else { + tracing::info!( + "Sample missed: missed {} samples from {:?}.", + source_sn - state.last_delivered.unwrap() - 1, + source_id, + ); + if let Some(miss_callback) = states.miss_callback.as_ref() { + (miss_callback)(*source_id, source_sn - state.last_delivered.unwrap() - 1); + states.callback.call(sample); + state.last_delivered = Some(source_sn); + } + } } } else { states.callback.call(sample); @@ -498,6 +512,7 @@ impl AdvancedSubscriber { }) }), key_expr: key_expr.clone().into_owned(), + retransmission: retransmission.is_some(), query_target: conf.query_target, query_timeout: conf.query_timeout, callback: callback.clone(), From 52bec1728b59cd1e010894d2acf21e2b6e355fd5 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 14:04:49 +0100 Subject: [PATCH 34/88] Add miss_sample_callback to DataSubscriberBuilderExt --- zenoh-ext/src/subscriber_ext.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 6742517edb..78f3641d3b 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -20,6 +20,7 @@ use zenoh::{ pubsub::{Subscriber, SubscriberBuilder}, query::{QueryConsolidation, QueryTarget, ReplyKeyExpr}, sample::{Locality, Sample}, + session::EntityGlobalId, Result as ZResult, }; @@ -129,13 +130,18 @@ pub trait DataSubscriberBuilderExt<'a, 'b, Handler> { /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that enable caching. - fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; // TODO take HistoryConf as parameter + fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; /// Ask for retransmission of detected lost Samples. /// /// Retransmission can only be achieved by Publishers that enable /// caching and sample_miss_detection. fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + + fn sample_miss_callback( + self, + callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, + ) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; } impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilder<'a, 'b, Handler> { @@ -261,6 +267,14 @@ impl<'a, 'b, Handler> DataSubscriberBuilderExt<'a, 'b, Handler> AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) .recovery(conf) } + + fn sample_miss_callback( + self, + callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, + ) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) + .sample_miss_callback(callback) + } } impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> From a16d64cdf138222444da0b1bedec143008ca7311 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 14:10:57 +0100 Subject: [PATCH 35/88] Add sample_miss_detection to PublisherBuilderExt --- zenoh-ext/src/advanced_publisher.rs | 6 ++++-- zenoh-ext/src/publisher_ext.rs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 146ade4fc1..38560a1b58 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -33,6 +33,7 @@ use crate::{ SessionExt, }; +#[derive(PartialEq)] pub enum Sequencing { None, Timestamp, @@ -69,7 +70,6 @@ impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { /// /// Retransmission can only be achieved if history is enabled. pub fn sample_miss_detection(mut self) -> Self { - self.cache = true; self.sequencing = Sequencing::SequenceNumber; self } @@ -77,7 +77,9 @@ impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { /// Change the history size for each resource. pub fn cache(mut self, config: CacheConfig) -> Self { self.cache = true; - self.sequencing = Sequencing::Timestamp; + if self.sequencing == Sequencing::None { + self.sequencing = Sequencing::Timestamp; + } self.history = config; self } diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index a0e47818e4..620b476c5b 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -24,6 +24,11 @@ pub trait PublisherBuilderExt<'a, 'b> { /// Retransmission can only be achieved if history is enabled. fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b>; + /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. + /// + /// Retransmission can only be achieved if cache is enabled. + fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b>; + /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. @@ -39,6 +44,13 @@ impl<'a, 'b> PublisherBuilderExt<'a, 'b> for PublisherBuilder<'a, 'b> { AdvancedPublisherBuilder::new(self.session, self.key_expr).cache(config) } + /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. + /// + /// Retransmission can only be achieved if cache is enabled. + fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b> { + AdvancedPublisherBuilder::new(self.session, self.key_expr).sample_miss_detection() + } + /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. From 5662f34497e520e7ed5c177b42b18e27afd07ba9 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 14:11:13 +0100 Subject: [PATCH 36/88] Add test_advanced_sample_miss test --- zenoh-ext/tests/advanced.rs | 128 +++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index e706491a29..0f72e21b1e 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -358,7 +358,7 @@ async fn test_advanced_retransmission_periodic() { } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn test_advanced_retransmission_sample_miss() { +async fn test_advanced_sample_miss() { use std::time::Duration; use zenoh::internal::ztimeout; @@ -368,6 +368,130 @@ async fn test_advanced_retransmission_sample_miss() { const RECONNECT_SLEEP: Duration = Duration::from_secs(5); const ROUTER_ENDPOINT: &str = "tcp/localhost:47453"; + const ADVANCED_SAMPLE_MISS_KEYEXPR: &str = "test/advanced/sample_miss"; + + zenoh_util::init_log_from_env_or("error"); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + + let client1 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (1) ZID: {}", s.zid()); + s + }; + + let client2 = { + let mut c = zenoh::Config::default(); + c.connect + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Client)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Client (2) ZID: {}", s.zid()); + s + }; + + let (miss_sender, miss_receiver) = flume::unbounded(); + + let sub = ztimeout!(client2 + .declare_subscriber(ADVANCED_SAMPLE_MISS_KEYEXPR) + .sample_miss_callback(move |s, m| { + miss_sender.send((s, m)).unwrap(); + })) + .unwrap(); + tokio::time::sleep(SLEEP).await; + + let publ = ztimeout!(client1 + .declare_publisher(ADVANCED_SAMPLE_MISS_KEYEXPR) + .sample_miss_detection()) + .unwrap(); + ztimeout!(publ.put("1")).unwrap(); + + tokio::time::sleep(SLEEP).await; + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "1"); + + assert!(sub.try_recv().unwrap().is_none()); + + router.close().await.unwrap(); + tokio::time::sleep(SLEEP).await; + + ztimeout!(publ.put("2")).unwrap(); + tokio::time::sleep(SLEEP).await; + + assert!(sub.try_recv().unwrap().is_none()); + + let router = { + let mut c = zenoh::Config::default(); + c.listen + .endpoints + .set(vec![ROUTER_ENDPOINT.parse::().unwrap()]) + .unwrap(); + c.scouting.multicast.set_enabled(Some(false)).unwrap(); + let _ = c.set_mode(Some(WhatAmI::Router)); + let s = ztimeout!(zenoh::open(c)).unwrap(); + tracing::info!("Router ZID: {}", s.zid()); + s + }; + tokio::time::sleep(RECONNECT_SLEEP).await; + + ztimeout!(publ.put("3")).unwrap(); + tokio::time::sleep(SLEEP).await; + + let miss = ztimeout!(miss_receiver.recv_async()).unwrap(); + assert_eq!(miss.0, publ.id()); + assert_eq!(miss.1, 1); + + assert!(miss_receiver.try_recv().is_err()); + + let sample = ztimeout!(sub.recv_async()).unwrap(); + assert_eq!(sample.kind(), SampleKind::Put); + assert_eq!(sample.payload().try_to_string().unwrap().as_ref(), "3"); + + assert!(sub.try_recv().unwrap().is_none()); + + publ.undeclare().await.unwrap(); + // sub.undeclare().await.unwrap(); + + client1.close().await.unwrap(); + client2.close().await.unwrap(); + + router.close().await.unwrap(); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_advanced_retransmission_sample_miss() { + use std::time::Duration; + + use zenoh::internal::ztimeout; + + const TIMEOUT: Duration = Duration::from_secs(60); + const SLEEP: Duration = Duration::from_secs(1); + const RECONNECT_SLEEP: Duration = Duration::from_secs(5); + const ROUTER_ENDPOINT: &str = "tcp/localhost:47454"; + const ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR: &str = "test/advanced/retransmission/sample_miss"; @@ -499,7 +623,7 @@ async fn test_advanced_late_joiner() { const TIMEOUT: Duration = Duration::from_secs(60); const SLEEP: Duration = Duration::from_secs(1); const RECONNECT_SLEEP: Duration = Duration::from_secs(8); - const ROUTER_ENDPOINT: &str = "tcp/localhost:47454"; + const ROUTER_ENDPOINT: &str = "tcp/localhost:47455"; const ADVANCED_LATE_JOINER_KEYEXPR: &str = "test/advanced/late_joiner"; From 6da5655f2175b279ac6334332f6f432363c3737a Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 17:27:47 +0100 Subject: [PATCH 37/88] Deliver sample even when no miss callback --- zenoh-ext/src/advanced_subscriber.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 83a9136ed8..9fa7094809 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -389,9 +389,9 @@ fn handle_sample(states: &mut State, sample: Sample) -> bool { ); if let Some(miss_callback) = states.miss_callback.as_ref() { (miss_callback)(*source_id, source_sn - state.last_delivered.unwrap() - 1); - states.callback.call(sample); - state.last_delivered = Some(source_sn); } + states.callback.call(sample); + state.last_delivered = Some(source_sn); } } } else { From 3449d735760650fa903907d83c8aca49083e78e6 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 28 Nov 2024 18:54:11 +0100 Subject: [PATCH 38/88] Replace sample_miss_callback with sample_miss_listener --- zenoh-ext/Cargo.toml | 2 +- zenoh-ext/examples/examples/z_advanced_sub.rs | 44 ++- zenoh-ext/src/advanced_subscriber.rs | 290 ++++++++++++++++-- zenoh-ext/src/subscriber_ext.rs | 14 +- zenoh-ext/tests/advanced.rs | 31 +- 5 files changed, 307 insertions(+), 74 deletions(-) diff --git a/zenoh-ext/Cargo.toml b/zenoh-ext/Cargo.toml index d433aa137c..4fdf98b8df 100644 --- a/zenoh-ext/Cargo.toml +++ b/zenoh-ext/Cargo.toml @@ -47,7 +47,7 @@ tracing = { workspace = true } serde = { workspace = true, features = ["default"] } leb128 = { workspace = true } uhlc = { workspace = true } -zenoh = { workspace = true, default-features = false } +zenoh = { workspace = true, features = ["default"] } zenoh-macros = { workspace = true } [dev-dependencies] diff --git a/zenoh-ext/examples/examples/z_advanced_sub.rs b/zenoh-ext/examples/examples/z_advanced_sub.rs index f4167549f1..9ced690d3f 100644 --- a/zenoh-ext/examples/examples/z_advanced_sub.rs +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -28,29 +28,43 @@ async fn main() { println!("Opening session..."); let session = zenoh::open(config).await.unwrap(); - println!("Declaring AdvancedSubscriber on {}", key_expr,); + println!("Declaring AdvancedSubscriber on {}", key_expr); let subscriber = session .declare_subscriber(key_expr) .history(HistoryConfig::default().late_joiner()) .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1)))) - .sample_miss_callback(|s, m| { - println!(">> [Subscriber] Missed {} samples from {:?} !!!", m, s); - }) .await .unwrap(); + let miss_listener = subscriber.sample_miss_listener().await.unwrap(); + println!("Press CTRL-C to quit..."); - while let Ok(sample) = subscriber.recv_async().await { - let payload = sample - .payload() - .try_to_string() - .unwrap_or_else(|e| e.to_string().into()); - println!( - ">> [Subscriber] Received {} ('{}': '{}')", - sample.kind(), - sample.key_expr().as_str(), - payload - ); + loop { + tokio::select! { + sample = subscriber.recv_async() => { + if let Ok(sample) = sample { + let payload = sample + .payload() + .try_to_string() + .unwrap_or_else(|e| e.to_string().into()); + println!( + ">> [Subscriber] Received {} ('{}': '{}')", + sample.kind(), + sample.key_expr().as_str(), + payload + ); + } + }, + miss = miss_listener.recv_async() => { + if let Ok(miss) = miss { + println!( + ">> [Subscriber] Missed {} samples from {:?} !!!", + miss.nb(), + miss.source() + ); + } + }, + } } } diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 9fa7094809..2d4d71e522 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -106,7 +106,6 @@ pub struct AdvancedSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = f pub(crate) session: &'a Session, pub(crate) key_expr: ZResult>, pub(crate) origin: Locality, - pub(crate) sample_miss_callback: Option>, pub(crate) retransmission: Option, pub(crate) query_target: QueryTarget, pub(crate) query_timeout: Duration, @@ -127,7 +126,6 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { key_expr, origin, handler, - sample_miss_callback: None, retransmission: None, query_target: QueryTarget::All, query_timeout: Duration::from_secs(10), @@ -151,7 +149,6 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { session: self.session, key_expr: self.key_expr.map(|s| s.into_owned()), origin: self.origin, - sample_miss_callback: self.sample_miss_callback, retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, @@ -185,7 +182,6 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { session: self.session, key_expr: self.key_expr.map(|s| s.into_owned()), origin: self.origin, - sample_miss_callback: self.sample_miss_callback, retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, @@ -206,16 +202,6 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { self } - #[zenoh_macros::unstable] - #[inline] - pub fn sample_miss_callback( - mut self, - callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, - ) -> Self { - self.sample_miss_callback = Some(Arc::new(callback)); - self - } - /// Ask for retransmission of detected lost Samples. /// /// Retransmission can only be achieved by Publishers that enable @@ -258,7 +244,6 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { session: self.session, key_expr: self.key_expr.map(|s| s.into_owned()), origin: self.origin, - sample_miss_callback: self.sample_miss_callback, retransmission: self.retransmission, query_target: self.query_target, query_timeout: self.query_timeout, @@ -306,6 +291,7 @@ struct Period { #[zenoh_macros::unstable] struct State { + next_id: usize, global_pending_queries: u64, sequenced_states: HashMap>, timestamped_states: HashMap>, @@ -316,7 +302,19 @@ struct State { query_target: QueryTarget, query_timeout: Duration, callback: Callback, - miss_callback: Option>, + miss_handlers: HashMap>, +} + +impl State { + fn register_miss_callback(&mut self, callback: Callback) -> usize { + let id = self.next_id; + self.next_id += 1; + self.miss_handlers.insert(id, callback); + id + } + fn unregister_miss_callback(&mut self, id: &usize) { + self.miss_handlers.remove(id); + } } macro_rules! spawn_periodoic_queries { @@ -342,6 +340,7 @@ struct SourceState { #[zenoh_macros::unstable] pub struct AdvancedSubscriber { + statesref: Arc>, _subscriber: Subscriber<()>, receiver: Receiver, _liveliness_subscriber: Option>, @@ -387,8 +386,11 @@ fn handle_sample(states: &mut State, sample: Sample) -> bool { source_sn - state.last_delivered.unwrap() - 1, source_id, ); - if let Some(miss_callback) = states.miss_callback.as_ref() { - (miss_callback)(*source_id, source_sn - state.last_delivered.unwrap() - 1); + for miss_callback in states.miss_handlers.values() { + miss_callback.call(Miss { + source: *source_id, + nb: source_sn - state.last_delivered.unwrap() - 1, + }); } states.callback.call(sample); state.last_delivered = Some(source_sn); @@ -501,6 +503,7 @@ impl AdvancedSubscriber { let query_timeout = conf.query_timeout; let session = conf.session.clone(); let statesref = Arc::new(Mutex::new(State { + next_id: 0, sequenced_states: HashMap::new(), timestamped_states: HashMap::new(), global_pending_queries: if conf.history.is_some() { 1 } else { 0 }, @@ -516,7 +519,7 @@ impl AdvancedSubscriber { query_target: conf.query_target, query_timeout: conf.query_timeout, callback: callback.clone(), - miss_callback: conf.sample_miss_callback, + miss_handlers: HashMap::new(), })); let sub_callback = { @@ -751,6 +754,7 @@ impl AdvancedSubscriber { }; let reliable_subscriber = AdvancedSubscriber { + statesref, _subscriber: subscriber, receiver, _liveliness_subscriber: liveliness_subscriber, @@ -759,6 +763,14 @@ impl AdvancedSubscriber { Ok(reliable_subscriber) } + #[zenoh_macros::unstable] + pub fn sample_miss_listener(&self) -> SampleMissListenerBuilder<'_, DefaultHandler> { + SampleMissListenerBuilder { + statesref: &self.statesref, + handler: DefaultHandler::default(), + } + } + /// Close this AdvancedSubscriber #[inline] pub fn close(self) -> impl Resolve> { @@ -772,7 +784,7 @@ fn flush_sequenced_source( state: &mut SourceState, callback: &Callback, source_id: &EntityGlobalId, - miss_callback: Option<&Arc>, + miss_handlers: &HashMap>, ) { if state.pending_queries == 0 && !state.pending_samples.is_empty() { let mut pending_samples = state @@ -796,8 +808,11 @@ fn flush_sequenced_source( seq_num - last - 1, source_id, ); - if let Some(miss_callback) = miss_callback { - (miss_callback)(*source_id, seq_num - last - 1) + for miss_callback in miss_handlers.values() { + miss_callback.call(Miss { + source: *source_id, + nb: seq_num - last - 1, + }) } state.last_delivered = Some(seq_num); callback.call(sample); @@ -846,12 +861,7 @@ impl Drop for InitialRepliesHandler { if states.global_pending_queries == 0 { for (source_id, state) in states.sequenced_states.iter_mut() { - flush_sequenced_source( - state, - &states.callback, - source_id, - states.miss_callback.as_ref(), - ); + flush_sequenced_source(state, &states.callback, source_id, &states.miss_handlers); spawn_periodoic_queries!(states, *source_id, self.statesref.clone()); } for state in states.timestamped_states.values_mut() { @@ -879,7 +889,7 @@ impl Drop for SequencedRepliesHandler { state, &states.callback, &self.source_id, - states.miss_callback.as_ref(), + &states.miss_handlers, ) } } @@ -906,3 +916,225 @@ impl Drop for TimestampedRepliesHandler { } } } + +#[zenoh_macros::unstable] +pub struct Miss { + source: EntityGlobalId, + nb: u32, +} + +impl Miss { + pub fn source(&self) -> EntityGlobalId { + self.source + } + pub fn nb(&self) -> u32 { + self.nb + } +} + +#[zenoh_macros::unstable] +pub struct SampleMissListener { + id: usize, + statesref: Arc>, + handler: Handler, +} + +#[zenoh_macros::unstable] +impl SampleMissListener { + #[inline] + pub fn undeclare(self) -> SampleMissHandlerUndeclaration + where + Handler: Send, + { + // self.undeclare_inner(()) + SampleMissHandlerUndeclaration(self) + } + + fn undeclare_impl(&mut self) -> ZResult<()> { + // set the flag first to avoid double panic if this function panic + zlock!(self.statesref).unregister_miss_callback(&self.id); + Ok(()) + } +} + +#[cfg(feature = "unstable")] +impl Drop for SampleMissListener { + fn drop(&mut self) { + if let Err(error) = self.undeclare_impl() { + tracing::error!(error); + } + } +} + +// #[zenoh_macros::unstable] +// impl UndeclarableSealed<()> for SampleMissHandler { +// type Undeclaration = SampleMissHandlerUndeclaration; + +// fn undeclare_inner(self, _: ()) -> Self::Undeclaration { +// SampleMissHandlerUndeclaration(self) +// } +// } + +#[zenoh_macros::unstable] +impl std::ops::Deref for SampleMissListener { + type Target = Handler; + + fn deref(&self) -> &Self::Target { + &self.handler + } +} +#[zenoh_macros::unstable] +impl std::ops::DerefMut for SampleMissListener { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.handler + } +} + +#[zenoh_macros::unstable] +pub struct SampleMissHandlerUndeclaration(SampleMissListener); + +#[zenoh_macros::unstable] +impl Resolvable for SampleMissHandlerUndeclaration { + type To = ZResult<()>; +} + +#[zenoh_macros::unstable] +impl Wait for SampleMissHandlerUndeclaration { + fn wait(mut self) -> ::To { + self.0.undeclare_impl() + } +} + +#[zenoh_macros::unstable] +impl IntoFuture for SampleMissHandlerUndeclaration { + type Output = ::To; + type IntoFuture = Ready<::To>; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} + +/// A builder for initializing a [`SampleMissHandler`]. +#[zenoh_macros::unstable] +pub struct SampleMissListenerBuilder<'a, Handler, const BACKGROUND: bool = false> { + statesref: &'a Arc>, + handler: Handler, +} + +#[zenoh_macros::unstable] +impl<'a> SampleMissListenerBuilder<'a, DefaultHandler> { + /// Receive the sample miss notification with a callback. + #[inline] + #[zenoh_macros::unstable] + pub fn callback(self, callback: F) -> SampleMissListenerBuilder<'a, Callback> + where + F: Fn(Miss) + Send + Sync + 'static, + { + self.with(Callback::new(Arc::new(callback))) + } + + /// Receive the sample miss notification with a mutable callback. + #[inline] + #[zenoh_macros::unstable] + pub fn callback_mut(self, callback: F) -> SampleMissListenerBuilder<'a, Callback> + where + F: FnMut(Miss) + Send + Sync + 'static, + { + self.callback(zenoh::handlers::locked(callback)) + } + + /// Receive the sample miss notification with a [`Handler`](IntoHandler). + #[inline] + #[zenoh_macros::unstable] + pub fn with(self, handler: Handler) -> SampleMissListenerBuilder<'a, Handler> + where + Handler: IntoHandler, + { + SampleMissListenerBuilder { + statesref: self.statesref, + handler, + } + } +} + +#[zenoh_macros::unstable] +impl<'a> SampleMissListenerBuilder<'a, Callback> { + /// Register the sample miss notification callback to be run in background until the adanced subscriber is undeclared. + /// + /// Background builder doesn't return a `SampleMissHandler` object anymore. + pub fn background(self) -> SampleMissListenerBuilder<'a, Callback, true> { + SampleMissListenerBuilder { + statesref: self.statesref, + handler: self.handler, + } + } +} + +#[zenoh_macros::unstable] +impl Resolvable for SampleMissListenerBuilder<'_, Handler> +where + Handler: IntoHandler + Send, + Handler::Handler: Send, +{ + type To = ZResult>; +} + +#[zenoh_macros::unstable] +impl Wait for SampleMissListenerBuilder<'_, Handler> +where + Handler: IntoHandler + Send, + Handler::Handler: Send, +{ + #[zenoh_macros::unstable] + fn wait(self) -> ::To { + let (callback, handler) = self.handler.into_handler(); + let id = zlock!(self.statesref).register_miss_callback(callback); + Ok(SampleMissListener { + id, + statesref: self.statesref.clone(), + handler, + }) + } +} + +#[zenoh_macros::unstable] +impl IntoFuture for SampleMissListenerBuilder<'_, Handler> +where + Handler: IntoHandler + Send, + Handler::Handler: Send, +{ + type Output = ::To; + type IntoFuture = Ready<::To>; + + #[zenoh_macros::unstable] + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} + +#[zenoh_macros::unstable] +impl Resolvable for SampleMissListenerBuilder<'_, Callback, true> { + type To = ZResult<()>; +} + +#[zenoh_macros::unstable] +impl Wait for SampleMissListenerBuilder<'_, Callback, true> { + #[zenoh_macros::unstable] + fn wait(self) -> ::To { + let (callback, _) = self.handler.into_handler(); + zlock!(self.statesref).register_miss_callback(callback); + Ok(()) + } +} + +#[zenoh_macros::unstable] +impl IntoFuture for SampleMissListenerBuilder<'_, Callback, true> { + type Output = ::To; + type IntoFuture = Ready<::To>; + + #[zenoh_macros::unstable] + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 78f3641d3b..d055312227 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -20,7 +20,6 @@ use zenoh::{ pubsub::{Subscriber, SubscriberBuilder}, query::{QueryConsolidation, QueryTarget, ReplyKeyExpr}, sample::{Locality, Sample}, - session::EntityGlobalId, Result as ZResult, }; @@ -138,10 +137,8 @@ pub trait DataSubscriberBuilderExt<'a, 'b, Handler> { /// caching and sample_miss_detection. fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; - fn sample_miss_callback( - self, - callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, - ) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + /// Turn this `Subscriber`into an `AdvancedSubscriber`. + fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; } impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilder<'a, 'b, Handler> { @@ -268,12 +265,9 @@ impl<'a, 'b, Handler> DataSubscriberBuilderExt<'a, 'b, Handler> .recovery(conf) } - fn sample_miss_callback( - self, - callback: impl Fn(EntityGlobalId, u32) + Send + Sync + 'static, - ) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + /// Turn this `Subscriber`into an `AdvancedSubscriber`. + fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .sample_miss_callback(callback) } } diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index 0f72e21b1e..683cc0dbb2 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -411,14 +411,11 @@ async fn test_advanced_sample_miss() { s }; - let (miss_sender, miss_receiver) = flume::unbounded(); - let sub = ztimeout!(client2 .declare_subscriber(ADVANCED_SAMPLE_MISS_KEYEXPR) - .sample_miss_callback(move |s, m| { - miss_sender.send((s, m)).unwrap(); - })) + .advanced()) .unwrap(); + let miss_listener = ztimeout!(sub.sample_miss_listener()).unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(client1 @@ -460,11 +457,11 @@ async fn test_advanced_sample_miss() { ztimeout!(publ.put("3")).unwrap(); tokio::time::sleep(SLEEP).await; - let miss = ztimeout!(miss_receiver.recv_async()).unwrap(); - assert_eq!(miss.0, publ.id()); - assert_eq!(miss.1, 1); + let miss = ztimeout!(miss_listener.recv_async()).unwrap(); + assert_eq!(miss.source(), publ.id()); + assert_eq!(miss.nb(), 1); - assert!(miss_receiver.try_recv().is_err()); + assert!(miss_listener.try_recv().is_err()); let sample = ztimeout!(sub.recv_async()).unwrap(); assert_eq!(sample.kind(), SampleKind::Put); @@ -536,15 +533,11 @@ async fn test_advanced_retransmission_sample_miss() { s }; - let (miss_sender, miss_receiver) = flume::unbounded(); - let sub = ztimeout!(client2 .declare_subscriber(ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR) - .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1)))) - .sample_miss_callback(move |s, m| { - miss_sender.send((s, m)).unwrap(); - })) + .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1))))) .unwrap(); + let miss_listener = ztimeout!(sub.sample_miss_listener()).unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(client1 @@ -589,11 +582,11 @@ async fn test_advanced_retransmission_sample_miss() { ztimeout!(publ.put("5")).unwrap(); tokio::time::sleep(SLEEP).await; - let miss = ztimeout!(miss_receiver.recv_async()).unwrap(); - assert_eq!(miss.0, publ.id()); - assert_eq!(miss.1, 2); + let miss = ztimeout!(miss_listener.recv_async()).unwrap(); + assert_eq!(miss.source(), publ.id()); + assert_eq!(miss.nb(), 2); - assert!(miss_receiver.try_recv().is_err()); + assert!(miss_listener.try_recv().is_err()); let sample = ztimeout!(sub.recv_async()).unwrap(); assert_eq!(sample.kind(), SampleKind::Put); From ba72a96ad063a78e2c1ad375d3949d17af345401 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 29 Nov 2024 17:43:08 +0100 Subject: [PATCH 39/88] Fix clippy warnings --- zenoh-ext/src/advanced_cache.rs | 2 +- zenoh-ext/src/advanced_publisher.rs | 2 +- zenoh-ext/src/advanced_subscriber.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 85b26264ae..74277a99e1 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -152,7 +152,7 @@ impl Wait for AdvancedCacheBuilder<'_, '_, '_> { } } -impl<'a> IntoFuture for AdvancedCacheBuilder<'a, '_, '_> { +impl IntoFuture for AdvancedCacheBuilder<'_, '_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 38560a1b58..737bcba7ff 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -103,7 +103,7 @@ impl Wait for AdvancedPublisherBuilder<'_, '_> { } } -impl<'a> IntoFuture for AdvancedPublisherBuilder<'a, '_> { +impl IntoFuture for AdvancedPublisherBuilder<'_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 2d4d71e522..3c3d1734ea 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -192,7 +192,7 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { } #[zenoh_macros::unstable] -impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { +impl<'a, Handler> AdvancedSubscriberBuilder<'a, '_, Handler> { /// Restrict the matching publications that will be receive by this [`Subscriber`] /// to the ones that have the given [`Locality`](crate::prelude::Locality). #[zenoh_macros::unstable] From 4df529e3fcb3332285beaa411d940f6c05336ba6 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 29 Nov 2024 22:03:09 +0100 Subject: [PATCH 40/88] Fix tests --- zenoh-ext/tests/advanced.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index 683cc0dbb2..7933350e46 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -461,7 +461,7 @@ async fn test_advanced_sample_miss() { assert_eq!(miss.source(), publ.id()); assert_eq!(miss.nb(), 1); - assert!(miss_listener.try_recv().is_err()); + assert!(miss_listener.try_recv().unwrap().is_none()); let sample = ztimeout!(sub.recv_async()).unwrap(); assert_eq!(sample.kind(), SampleKind::Put); @@ -586,7 +586,7 @@ async fn test_advanced_retransmission_sample_miss() { assert_eq!(miss.source(), publ.id()); assert_eq!(miss.nb(), 2); - assert!(miss_listener.try_recv().is_err()); + assert!(miss_listener.try_recv().unwrap().is_none()); let sample = ztimeout!(sub.recv_async()).unwrap(); assert_eq!(sample.kind(), SampleKind::Put); From 443d5405f50aef042d8e8646f800b2157275d671 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 29 Nov 2024 22:53:34 +0100 Subject: [PATCH 41/88] Add HistoryConf max_samples option --- zenoh-ext/src/advanced_cache.rs | 72 +++++--- zenoh-ext/src/advanced_subscriber.rs | 239 +++++++++++++++------------ 2 files changed, 179 insertions(+), 132 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 74277a99e1..ab2a148a5d 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -231,6 +231,51 @@ impl AdvancedCache { let (stoptx, stoprx) = bounded::(1); task::spawn(async move { + async fn process_queue( + queue: &VecDeque, + query: &Query, + start: Option, + end: Option, + max: Option, + ) { + if let Some(max) = max { + let mut samples = VecDeque::new(); + for sample in queue { + if sample_in_range(sample, start, end) { + if let (Some(Ok(time_range)), Some(timestamp)) = + (query.parameters().time_range(), sample.timestamp()) + { + if !time_range.contains(timestamp.get_time().to_system_time()) { + continue; + } + } + samples.push_front(sample); + samples.truncate(max as usize); + } + } + for sample in samples.drain(..).rev() { + if let Err(e) = query.reply_sample(sample.clone()).await { + tracing::warn!("Error replying to query: {}", e); + } + } + } else { + for sample in queue { + if sample_in_range(sample, start, end) { + if let (Some(Ok(time_range)), Some(timestamp)) = + (query.parameters().time_range(), sample.timestamp()) + { + if !time_range.contains(timestamp.get_time().to_system_time()) { + continue; + } + } + if let Err(e) = query.reply_sample(sample.clone()).await { + tracing::warn!("Error replying to query: {}", e); + } + } + } + } + } + let mut cache: HashMap> = HashMap::with_capacity(history.resources_limit.unwrap_or(32)); let limit = history.resources_limit.unwrap_or(usize::MAX); @@ -266,36 +311,15 @@ impl AdvancedCache { query = quer_recv.recv_async() => { if let Ok(query) = query { let (start, end) = query.parameters().get("_sn").map(decode_range).unwrap_or((None, None)); + let max = query.parameters().get("_max").and_then(|s| s.parse::().ok()); if !query.selector().key_expr().as_str().contains('*') { if let Some(queue) = cache.get(query.selector().key_expr().as_keyexpr()) { - for sample in queue { - if sample_in_range(sample, start, end) { - if let (Some(Ok(time_range)), Some(timestamp)) = (query.parameters().time_range(), sample.timestamp()) { - if !time_range.contains(timestamp.get_time().to_system_time()){ - continue; - } - } - if let Err(e) = query.reply_sample(sample.clone()).await { - tracing::warn!("Error replying to query: {}", e); - } - } - } + process_queue(queue, &query, start, end, max).await; } } else { for (key_expr, queue) in cache.iter() { if query.selector().key_expr().intersects(key_expr.borrow()) { - for sample in queue { - if sample_in_range(sample, start, end) { - if let (Some(Ok(time_range)), Some(timestamp)) = (query.parameters().time_range(), sample.timestamp()) { - if !time_range.contains(timestamp.get_time().to_system_time()){ - continue; - } - } - if let Err(e) = query.reply_sample(sample.clone()).await { - tracing::warn!("Error replying to query: {}", e); - } - } - } + process_queue(queue, &query, start, end, max).await; } } } diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 3c3d1734ea..3f7c385825 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -17,7 +17,7 @@ use zenoh::{ config::ZenohId, handlers::{Callback, IntoHandler}, key_expr::KeyExpr, - query::{ConsolidationMode, Selector}, + query::{ConsolidationMode, Parameters, Selector}, sample::{Locality, Sample, SampleKind}, session::{EntityGlobalId, EntityId}, Resolvable, Resolve, Session, Wait, @@ -47,7 +47,7 @@ use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; /// Configure query for historical data. pub struct HistoryConfig { liveliness: bool, - // sample_depth: usize, + sample_depth: Option, } impl HistoryConfig { @@ -62,11 +62,11 @@ impl HistoryConfig { self } - // /// Specify how many samples to keep for each resource. - // pub fn max_samples(mut self, depth: usize) -> Self { - // self.sample_depth = depth; - // self - // } + /// Specify how many samples to query for each resource. + pub fn max_samples(mut self, depth: usize) -> Self { + self.sample_depth = Some(depth); + self + } // TODO pub fn max_age(mut self, depth: Duration) -> Self } @@ -586,15 +586,19 @@ impl AdvancedSubscriber { .allowed_origin(conf.origin) .wait()?; - if conf.history.is_some() { + if let Some(historyconf) = conf.history.as_ref() { let handler = InitialRepliesHandler { statesref: statesref.clone(), }; + let mut params = Parameters::empty(); + if let Some(max) = historyconf.sample_depth { + params.insert("_max", max.to_string()); + } let _ = conf .session .get(Selector::from(( KE_PREFIX / KE_STAR / KE_STAR / &key_expr, - "0..", + params, ))) .callback({ let key_expr = key_expr.clone().into_owned(); @@ -614,69 +618,119 @@ impl AdvancedSubscriber { .wait(); } - let liveliness_subscriber = if conf.history.is_some_and(|h| h.liveliness) { - let live_callback = { - let session = conf.session.clone(); - let statesref = statesref.clone(); - let key_expr = key_expr.clone().into_owned(); - move |s: Sample| { - if s.kind() == SampleKind::Put { - if let Ok(parsed) = ke_liveliness::parse(s.key_expr().as_keyexpr()) { - if let Ok(zid) = ZenohId::from_str(parsed.zid().as_str()) { - // TODO : If we already have a state associated to this discovered source - // we should query with the appropriate range to avoid unnecessary retransmissions - if parsed.eid() == KE_UHLC { - let states = &mut *zlock!(statesref); - let entry = states.timestamped_states.entry(ID::from(zid)); - let state = entry.or_insert(SourceState:: { - last_delivered: None, - pending_queries: 0, - pending_samples: HashMap::new(), - }); - state.pending_queries += 1; - - let handler = TimestampedRepliesHandler { - id: ID::from(zid), - statesref: statesref.clone(), - callback: callback.clone(), - }; - let _ = session - .get(Selector::from((s.key_expr(), "0.."))) - .callback({ - let key_expr = key_expr.clone().into_owned(); - move |r: Reply| { - if let Ok(s) = r.into_result() { - if key_expr.intersects(s.key_expr()) { - let states = - &mut *zlock!(handler.statesref); - handle_sample(states, s); + let liveliness_subscriber = if let Some(historyconf) = conf.history { + if historyconf.liveliness { + let live_callback = { + let session = conf.session.clone(); + let statesref = statesref.clone(); + let key_expr = key_expr.clone().into_owned(); + move |s: Sample| { + if s.kind() == SampleKind::Put { + if let Ok(parsed) = ke_liveliness::parse(s.key_expr().as_keyexpr()) { + if let Ok(zid) = ZenohId::from_str(parsed.zid().as_str()) { + // TODO : If we already have a state associated to this discovered source + // we should query with the appropriate range to avoid unnecessary retransmissions + if parsed.eid() == KE_UHLC { + let states = &mut *zlock!(statesref); + let entry = states.timestamped_states.entry(ID::from(zid)); + let state = entry.or_insert(SourceState:: { + last_delivered: None, + pending_queries: 0, + pending_samples: HashMap::new(), + }); + state.pending_queries += 1; + + let handler = TimestampedRepliesHandler { + id: ID::from(zid), + statesref: statesref.clone(), + callback: callback.clone(), + }; + let mut params = Parameters::empty(); + if let Some(max) = historyconf.sample_depth { + params.insert("_max", max.to_string()); + } + let _ = session + .get(Selector::from((s.key_expr(), params))) + .callback({ + let key_expr = key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let states = + &mut *zlock!(handler.statesref); + handle_sample(states, s); + } } } - } - }) - .consolidation(ConsolidationMode::None) - .accept_replies(ReplyKeyExpr::Any) - .target(query_target) - .timeout(query_timeout) - .wait(); - } else if let Ok(eid) = EntityId::from_str(parsed.eid().as_str()) { - let source_id = EntityGlobalId::new(zid, eid); + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); + } else if let Ok(eid) = + EntityId::from_str(parsed.eid().as_str()) + { + let source_id = EntityGlobalId::new(zid, eid); + let states = &mut *zlock!(statesref); + let entry = states.sequenced_states.entry(source_id); + let new = matches!(&entry, Entry::Vacant(_)); + let state = entry.or_insert(SourceState:: { + last_delivered: None, + pending_queries: 0, + pending_samples: HashMap::new(), + }); + state.pending_queries += 1; + + let handler = SequencedRepliesHandler { + source_id, + statesref: statesref.clone(), + }; + let mut params = Parameters::empty(); + if let Some(max) = historyconf.sample_depth { + params.insert("_max", max.to_string()); + } + let _ = session + .get(Selector::from((s.key_expr(), params))) + .callback({ + let key_expr = key_expr.clone().into_owned(); + move |r: Reply| { + if let Ok(s) = r.into_result() { + if key_expr.intersects(s.key_expr()) { + let states = + &mut *zlock!(handler.statesref); + handle_sample(states, s); + } + } + } + }) + .consolidation(ConsolidationMode::None) + .accept_replies(ReplyKeyExpr::Any) + .target(query_target) + .timeout(query_timeout) + .wait(); + + if new { + spawn_periodoic_queries!( + states, + source_id, + statesref.clone() + ); + } + } + } else { let states = &mut *zlock!(statesref); - let entry = states.sequenced_states.entry(source_id); - let new = matches!(&entry, Entry::Vacant(_)); - let state = entry.or_insert(SourceState:: { - last_delivered: None, - pending_queries: 0, - pending_samples: HashMap::new(), - }); - state.pending_queries += 1; - - let handler = SequencedRepliesHandler { - source_id, + states.global_pending_queries += 1; + + let handler = InitialRepliesHandler { statesref: statesref.clone(), }; + let mut params = Parameters::empty(); + if let Some(max) = historyconf.sample_depth { + params.insert("_max", max.to_string()); + } let _ = session - .get(Selector::from((s.key_expr(), "0.."))) + .get(Selector::from((s.key_expr(), params))) .callback({ let key_expr = key_expr.clone().into_owned(); move |r: Reply| { @@ -694,53 +748,19 @@ impl AdvancedSubscriber { .target(query_target) .timeout(query_timeout) .wait(); - - if new { - spawn_periodoic_queries!( - states, - source_id, - statesref.clone() - ); - } } } else { - let states = &mut *zlock!(statesref); - states.global_pending_queries += 1; - - let handler = InitialRepliesHandler { - statesref: statesref.clone(), - }; - let _ = session - .get(Selector::from((s.key_expr(), "0.."))) - .callback({ - let key_expr = key_expr.clone().into_owned(); - move |r: Reply| { - if let Ok(s) = r.into_result() { - if key_expr.intersects(s.key_expr()) { - let states = &mut *zlock!(handler.statesref); - handle_sample(states, s); - } - } - } - }) - .consolidation(ConsolidationMode::None) - .accept_replies(ReplyKeyExpr::Any) - .target(query_target) - .timeout(query_timeout) - .wait(); + tracing::warn!( + "Received malformed liveliness token key expression: {}", + s.key_expr() + ); } - } else { - tracing::warn!( - "Received malformed liveliness token key expression: {}", - s.key_expr() - ); } } - } - }; + }; - Some( - conf + Some( + conf .session .liveliness() .declare_subscriber(KE_PREFIX / KE_STAR / KE_STAR / &key_expr) @@ -748,7 +768,10 @@ impl AdvancedSubscriber { .history(true) .callback(live_callback) .wait()?, - ) + ) + } else { + None + } } else { None }; From f9c7d0ee9a4f6eca591550ed51c620652cda2261 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Sat, 30 Nov 2024 00:22:51 +0100 Subject: [PATCH 42/88] Add HistoryConf max_age option --- zenoh-ext/src/advanced_subscriber.rs | 41 ++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 3f7c385825..3bf036fb44 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -17,7 +17,9 @@ use zenoh::{ config::ZenohId, handlers::{Callback, IntoHandler}, key_expr::KeyExpr, - query::{ConsolidationMode, Parameters, Selector}, + query::{ + ConsolidationMode, Parameters, Selector, TimeBound, TimeExpr, TimeRange, ZenohParameters, + }, sample::{Locality, Sample, SampleKind}, session::{EntityGlobalId, EntityId}, Resolvable, Resolve, Session, Wait, @@ -48,6 +50,7 @@ use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; pub struct HistoryConfig { liveliness: bool, sample_depth: Option, + age: Option, } impl HistoryConfig { @@ -68,7 +71,11 @@ impl HistoryConfig { self } - // TODO pub fn max_age(mut self, depth: Duration) -> Self + /// Specify the maximum age of samples to query. + pub fn max_age(mut self, seconds: f64) -> Self { + self.age = Some(seconds); + self + } } #[derive(Default)] @@ -594,6 +601,12 @@ impl AdvancedSubscriber { if let Some(max) = historyconf.sample_depth { params.insert("_max", max.to_string()); } + if let Some(age) = historyconf.age { + params.set_time_range(TimeRange { + start: TimeBound::Inclusive(TimeExpr::Now { offset_secs: -age }), + end: TimeBound::Unbounded, + }); + } let _ = conf .session .get(Selector::from(( @@ -649,6 +662,14 @@ impl AdvancedSubscriber { if let Some(max) = historyconf.sample_depth { params.insert("_max", max.to_string()); } + if let Some(age) = historyconf.age { + params.set_time_range(TimeRange { + start: TimeBound::Inclusive(TimeExpr::Now { + offset_secs: -age, + }), + end: TimeBound::Unbounded, + }); + } let _ = session .get(Selector::from((s.key_expr(), params))) .callback({ @@ -690,6 +711,14 @@ impl AdvancedSubscriber { if let Some(max) = historyconf.sample_depth { params.insert("_max", max.to_string()); } + if let Some(age) = historyconf.age { + params.set_time_range(TimeRange { + start: TimeBound::Inclusive(TimeExpr::Now { + offset_secs: -age, + }), + end: TimeBound::Unbounded, + }); + } let _ = session .get(Selector::from((s.key_expr(), params))) .callback({ @@ -729,6 +758,14 @@ impl AdvancedSubscriber { if let Some(max) = historyconf.sample_depth { params.insert("_max", max.to_string()); } + if let Some(age) = historyconf.age { + params.set_time_range(TimeRange { + start: TimeBound::Inclusive(TimeExpr::Now { + offset_secs: -age, + }), + end: TimeBound::Unbounded, + }); + } let _ = session .get(Selector::from((s.key_expr(), params))) .callback({ From 3b28b04828fcdaf0937dc8763cfe9de54e5cca0f Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 2 Dec 2024 22:02:01 +0100 Subject: [PATCH 43/88] Use BTreeMap --- zenoh-ext/src/advanced_subscriber.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 3bf036fb44..d6c5c38586 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -11,7 +11,7 @@ // Contributors: // ZettaScale Zenoh Team, // -use std::{future::IntoFuture, str::FromStr}; +use std::{collections::BTreeMap, future::IntoFuture, str::FromStr}; use zenoh::{ config::ZenohId, @@ -342,7 +342,7 @@ macro_rules! spawn_periodoic_queries { struct SourceState { last_delivered: Option, pending_queries: u64, - pending_samples: HashMap, + pending_samples: BTreeMap, } #[zenoh_macros::unstable] @@ -379,7 +379,7 @@ fn handle_sample(states: &mut State, sample: Sample) -> bool { let state = entry.or_insert(SourceState:: { last_delivered: None, pending_queries: 0, - pending_samples: HashMap::new(), + pending_samples: BTreeMap::new(), }); if states.global_pending_queries != 0 { state.pending_samples.insert(source_sn, sample); @@ -419,7 +419,7 @@ fn handle_sample(states: &mut State, sample: Sample) -> bool { let state = entry.or_insert(SourceState:: { last_delivered: None, pending_queries: 0, - pending_samples: HashMap::new(), + pending_samples: BTreeMap::new(), }); if state.last_delivered.map(|t| t < *timestamp).unwrap_or(true) { if states.global_pending_queries == 0 && state.pending_queries == 0 { @@ -649,7 +649,7 @@ impl AdvancedSubscriber { let state = entry.or_insert(SourceState:: { last_delivered: None, pending_queries: 0, - pending_samples: HashMap::new(), + pending_samples: BTreeMap::new(), }); state.pending_queries += 1; @@ -699,7 +699,7 @@ impl AdvancedSubscriber { let state = entry.or_insert(SourceState:: { last_delivered: None, pending_queries: 0, - pending_samples: HashMap::new(), + pending_samples: BTreeMap::new(), }); state.pending_queries += 1; @@ -847,11 +847,8 @@ fn flush_sequenced_source( miss_handlers: &HashMap>, ) { if state.pending_queries == 0 && !state.pending_samples.is_empty() { - let mut pending_samples = state - .pending_samples - .drain() - .collect::>(); - pending_samples.sort_by_key(|(k, _s)| *k); + let mut pending_samples = BTreeMap::new(); + std::mem::swap(&mut state.pending_samples, &mut pending_samples); for (seq_num, sample) in pending_samples { match state.last_delivered { None => { @@ -889,11 +886,8 @@ fn flush_sequenced_source( #[inline] fn flush_timestamped_source(state: &mut SourceState, callback: &Callback) { if state.pending_queries == 0 && !state.pending_samples.is_empty() { - let mut pending_samples = state - .pending_samples - .drain() - .collect::>(); - pending_samples.sort_by_key(|(k, _s)| *k); + let mut pending_samples = BTreeMap::new(); + std::mem::swap(&mut state.pending_samples, &mut pending_samples); for (timestamp, sample) in pending_samples { if state .last_delivered From 9e7a99ba534f7bc61c5c24c65d8d79e9f5c6f67f Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 4 Dec 2024 15:29:01 +0100 Subject: [PATCH 44/88] Add meta_keyexpr option --- zenoh-ext/src/advanced_cache.rs | 14 ++++++--- zenoh-ext/src/advanced_publisher.rs | 46 ++++++++++++++++++++-------- zenoh-ext/src/advanced_subscriber.rs | 10 ++++-- zenoh-ext/src/publisher_ext.rs | 16 +++++----- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index ab2a148a5d..4a33f32add 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -34,15 +34,21 @@ use zenoh::{ Resolvable, Resolve, Result as ZResult, Session, Wait, }; +// #[zenoh_macros::unstable] +// pub(crate) static KE_STAR: &keyexpr = ke!("*"); #[zenoh_macros::unstable] -pub(crate) static KE_STAR: &keyexpr = ke!("*"); +pub(crate) static KE_STARSTAR: &keyexpr = ke!("**"); #[zenoh_macros::unstable] -pub(crate) static KE_PREFIX: &keyexpr = ke!("@cache"); +pub(crate) static KE_PREFIX: &keyexpr = ke!("@adv"); +#[zenoh_macros::unstable] +pub(crate) static KE_SEPARATOR: &keyexpr = ke!("@"); #[zenoh_macros::unstable] pub(crate) static KE_UHLC: &keyexpr = ke!("uhlc"); #[zenoh_macros::unstable] +pub(crate) static KE_EMPTY: &keyexpr = ke!("_"); +#[zenoh_macros::unstable] kedefine!( - pub(crate) ke_liveliness: "@cache/${zid:*}/${eid:*}/${remaining:**}", + pub(crate) ke_liveliness: "@adv/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", ); #[derive(Debug, Clone)] @@ -96,7 +102,7 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { AdvancedCacheBuilder { session, pub_key_expr, - queryable_prefix: Some(Ok((KE_PREFIX / KE_STAR / KE_STAR).into())), + queryable_prefix: Some(Ok((KE_PREFIX / KE_STARSTAR / KE_SEPARATOR).into())), subscriber_origin: Locality::default(), queryable_origin: Locality::default(), history: CacheConfig::default(), diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 737bcba7ff..21d4c4bfb0 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -29,7 +29,7 @@ use zenoh::{ }; use crate::{ - advanced_cache::{AdvancedCache, CacheConfig, KE_PREFIX, KE_UHLC}, + advanced_cache::{AdvancedCache, CacheConfig, KE_EMPTY, KE_PREFIX, KE_SEPARATOR, KE_UHLC}, SessionExt, }; @@ -42,23 +42,25 @@ pub enum Sequencing { /// The builder of PublicationCache, allowing to configure it. #[must_use = "Resolvables do nothing unless you resolve them using the `res` method from either `SyncResolve` or `AsyncResolve`"] -pub struct AdvancedPublisherBuilder<'a, 'b> { +pub struct AdvancedPublisherBuilder<'a, 'b, 'c> { session: &'a Session, pub_key_expr: ZResult>, + meta_key_expr: Option>>, sequencing: Sequencing, liveliness: bool, cache: bool, history: CacheConfig, } -impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { +impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { pub(crate) fn new( session: &'a Session, pub_key_expr: ZResult>, - ) -> AdvancedPublisherBuilder<'a, 'b> { + ) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder { session, pub_key_expr, + meta_key_expr: None, sequencing: Sequencing::None, liveliness: false, cache: false, @@ -91,19 +93,31 @@ impl<'a, 'b> AdvancedPublisherBuilder<'a, 'b> { self.liveliness = true; self } + + /// A key expression added to the liveliness token key expression + /// and to the cache queryable key expression. + /// It can be used to convey meta data. + pub fn meta_keyexpr(mut self, meta: TryIntoKeyExpr) -> Self + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + self.meta_key_expr = Some(meta.try_into().map_err(Into::into)); + self + } } -impl<'a> Resolvable for AdvancedPublisherBuilder<'a, '_> { +impl<'a> Resolvable for AdvancedPublisherBuilder<'a, '_, '_> { type To = ZResult>; } -impl Wait for AdvancedPublisherBuilder<'_, '_> { +impl Wait for AdvancedPublisherBuilder<'_, '_, '_> { fn wait(self) -> ::To { AdvancedPublisher::new(self) } } -impl IntoFuture for AdvancedPublisherBuilder<'_, '_> { +impl IntoFuture for AdvancedPublisherBuilder<'_, '_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; @@ -120,21 +134,29 @@ pub struct AdvancedPublisher<'a> { } impl<'a> AdvancedPublisher<'a> { - fn new(conf: AdvancedPublisherBuilder<'a, '_>) -> ZResult { + fn new(conf: AdvancedPublisherBuilder<'a, '_, '_>) -> ZResult { let key_expr = conf.pub_key_expr?; + let meta = match conf.meta_key_expr { + Some(meta) => Some(meta?), + None => None, + }; let publisher = conf .session .declare_publisher(key_expr.clone().into_owned()) .wait()?; let id = publisher.id(); + let prefix = KE_PREFIX / &id.zid().into_keyexpr(); let prefix = match conf.sequencing { Sequencing::SequenceNumber => { - KE_PREFIX - / &id.zid().into_keyexpr() - / &KeyExpr::try_from(id.eid().to_string()).unwrap() + prefix / &KeyExpr::try_from(id.eid().to_string()).unwrap() } - _ => KE_PREFIX / &id.zid().into_keyexpr() / KE_UHLC, + _ => prefix / KE_UHLC, + }; + let prefix = match meta { + Some(meta) => prefix / &meta / KE_SEPARATOR, + // We need this empty chunk because af a routing matching bug + _ => prefix / KE_EMPTY / KE_SEPARATOR, }; let seqnum = match conf.sequencing { diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index d6c5c38586..58b41c3d3b 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -43,7 +43,7 @@ use { zenoh::Result as ZResult, }; -use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_STAR, KE_UHLC}; +use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_SEPARATOR, KE_STARSTAR, KE_UHLC}; #[derive(Debug, Default, Clone)] /// Configure query for historical data. @@ -464,6 +464,8 @@ impl Timed for PeriodicQuery { let query_expr = KE_PREFIX / &self.source_id.zid().into_keyexpr() / &KeyExpr::try_from(self.source_id.eid().to_string()).unwrap() + / KE_STARSTAR + / KE_SEPARATOR / &states.key_expr; let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); @@ -554,6 +556,8 @@ impl AdvancedSubscriber { let query_expr = KE_PREFIX / &source_id.zid().into_keyexpr() / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() + / KE_STARSTAR + / KE_SEPARATOR / &key_expr; let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); @@ -610,7 +614,7 @@ impl AdvancedSubscriber { let _ = conf .session .get(Selector::from(( - KE_PREFIX / KE_STAR / KE_STAR / &key_expr, + KE_PREFIX / KE_STARSTAR / KE_SEPARATOR / &key_expr, params, ))) .callback({ @@ -800,7 +804,7 @@ impl AdvancedSubscriber { conf .session .liveliness() - .declare_subscriber(KE_PREFIX / KE_STAR / KE_STAR / &key_expr) + .declare_subscriber(KE_PREFIX / KE_STARSTAR / KE_SEPARATOR / &key_expr) // .declare_subscriber(keformat!(ke_liveliness_all::formatter(), zid = 0, eid = 0, remaining = key_expr).unwrap()) .history(true) .callback(live_callback) diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index 620b476c5b..3555389daa 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -17,44 +17,44 @@ use crate::{advanced_cache::CacheConfig, AdvancedPublisherBuilder}; /// Some extensions to the [`zenoh::publication::PublisherBuilder`](zenoh::publication::PublisherBuilder) #[zenoh_macros::unstable] -pub trait PublisherBuilderExt<'a, 'b> { +pub trait PublisherBuilderExt<'a, 'b, 'c> { /// Allow matching Subscribers to detect lost samples and /// optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. - fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b>; + fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c>; /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. /// /// Retransmission can only be achieved if cache is enabled. - fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b>; + fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b>; + fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; } -impl<'a, 'b> PublisherBuilderExt<'a, 'b> for PublisherBuilder<'a, 'b> { +impl<'a, 'b, 'c> PublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { /// Allow matching Subscribers to detect lost samples and /// optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. - fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b> { + fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self.session, self.key_expr).cache(config) } /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. /// /// Retransmission can only be achieved if cache is enabled. - fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b> { + fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self.session, self.key_expr).sample_miss_detection() } /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b> { + fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self.session, self.key_expr).late_joiner_detection() } } From 43d9d7933df00275d14e3b61e8dd2391e82752c1 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 4 Dec 2024 15:53:00 +0100 Subject: [PATCH 45/88] Add late_joiner_detection and meta_keyexpr options on Subcriber side --- zenoh-ext/src/advanced_subscriber.rs | 81 +++++++++++++++++++++++----- zenoh-ext/src/subscriber_ext.rs | 16 +++--- 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 58b41c3d3b..2807192aaf 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -17,6 +17,7 @@ use zenoh::{ config::ZenohId, handlers::{Callback, IntoHandler}, key_expr::KeyExpr, + liveliness::LivelinessToken, query::{ ConsolidationMode, Parameters, Selector, TimeBound, TimeExpr, TimeRange, ZenohParameters, }, @@ -43,7 +44,9 @@ use { zenoh::Result as ZResult, }; -use crate::advanced_cache::{ke_liveliness, KE_PREFIX, KE_SEPARATOR, KE_STARSTAR, KE_UHLC}; +use crate::advanced_cache::{ + ke_liveliness, KE_EMPTY, KE_PREFIX, KE_SEPARATOR, KE_STARSTAR, KE_UHLC, +}; #[derive(Debug, Default, Clone)] /// Configure query for historical data. @@ -109,7 +112,7 @@ impl RecoveryConfig { /// The builder of AdvancedSubscriber, allowing to configure it. #[zenoh_macros::unstable] -pub struct AdvancedSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = false> { +pub struct AdvancedSubscriberBuilder<'a, 'b, 'c, Handler, const BACKGROUND: bool = false> { pub(crate) session: &'a Session, pub(crate) key_expr: ZResult>, pub(crate) origin: Locality, @@ -117,11 +120,13 @@ pub struct AdvancedSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = f pub(crate) query_target: QueryTarget, pub(crate) query_timeout: Duration, pub(crate) history: Option, + pub(crate) liveliness: bool, + pub(crate) meta_key_expr: Option>>, pub(crate) handler: Handler, } #[zenoh_macros::unstable] -impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { +impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, '_, Handler> { pub(crate) fn new( session: &'a Session, key_expr: ZResult>, @@ -137,18 +142,20 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, Handler> { query_target: QueryTarget::All, query_timeout: Duration::from_secs(10), history: None, + liveliness: false, + meta_key_expr: None, } } } #[zenoh_macros::unstable] -impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { +impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { /// Add callback to AdvancedSubscriber. #[inline] pub fn callback( self, callback: Callback, - ) -> AdvancedSubscriberBuilder<'a, 'b, Callback> + ) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback> where Callback: Fn(Sample) + Send + Sync + 'static, { @@ -160,6 +167,8 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { query_target: self.query_target, query_timeout: self.query_timeout, history: self.history, + liveliness: self.liveliness, + meta_key_expr: self.meta_key_expr, handler: callback, } } @@ -172,7 +181,7 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { pub fn callback_mut( self, callback: CallbackMut, - ) -> AdvancedSubscriberBuilder<'a, 'b, impl Fn(Sample) + Send + Sync + 'static> + ) -> AdvancedSubscriberBuilder<'a, 'b, 'c, impl Fn(Sample) + Send + Sync + 'static> where CallbackMut: FnMut(Sample) + Send + Sync + 'static, { @@ -181,7 +190,7 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { /// Make the built AdvancedSubscriber an [`AdvancedSubscriber`](AdvancedSubscriber). #[inline] - pub fn with(self, handler: Handler) -> AdvancedSubscriberBuilder<'a, 'b, Handler> + pub fn with(self, handler: Handler) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> where Handler: IntoHandler, { @@ -193,13 +202,15 @@ impl<'a, 'b> AdvancedSubscriberBuilder<'a, 'b, DefaultHandler> { query_target: self.query_target, query_timeout: self.query_timeout, history: self.history, + liveliness: self.liveliness, + meta_key_expr: self.meta_key_expr, handler, } } } #[zenoh_macros::unstable] -impl<'a, Handler> AdvancedSubscriberBuilder<'a, '_, Handler> { +impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { /// Restrict the matching publications that will be receive by this [`Subscriber`] /// to the ones that have the given [`Locality`](crate::prelude::Locality). #[zenoh_macros::unstable] @@ -246,7 +257,24 @@ impl<'a, Handler> AdvancedSubscriberBuilder<'a, '_, Handler> { self } - fn with_static_keys(self) -> AdvancedSubscriberBuilder<'a, 'static, Handler> { + /// Allow this subscriber to be detected through liveliness. + pub fn late_joiner_detection(mut self) -> Self { + self.liveliness = true; + self + } + + /// A key expression added to the liveliness token key expression. + /// It can be used to convey meta data. + pub fn meta_keyexpr(mut self, meta: TryIntoKeyExpr) -> Self + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + self.meta_key_expr = Some(meta.try_into().map_err(Into::into)); + self + } + + fn with_static_keys(self) -> AdvancedSubscriberBuilder<'a, 'static, 'static, Handler> { AdvancedSubscriberBuilder { session: self.session, key_expr: self.key_expr.map(|s| s.into_owned()), @@ -255,12 +283,14 @@ impl<'a, Handler> AdvancedSubscriberBuilder<'a, '_, Handler> { query_target: self.query_target, query_timeout: self.query_timeout, history: self.history, + liveliness: self.liveliness, + meta_key_expr: self.meta_key_expr.map(|s| s.map(|s| s.into_owned())), handler: self.handler, } } } -impl Resolvable for AdvancedSubscriberBuilder<'_, '_, Handler> +impl Resolvable for AdvancedSubscriberBuilder<'_, '_, '_, Handler> where Handler: IntoHandler, Handler::Handler: Send, @@ -268,7 +298,7 @@ where type To = ZResult>; } -impl Wait for AdvancedSubscriberBuilder<'_, '_, Handler> +impl Wait for AdvancedSubscriberBuilder<'_, '_, '_, Handler> where Handler: IntoHandler + Send, Handler::Handler: Send, @@ -278,7 +308,7 @@ where } } -impl IntoFuture for AdvancedSubscriberBuilder<'_, '_, Handler> +impl IntoFuture for AdvancedSubscriberBuilder<'_, '_, '_, Handler> where Handler: IntoHandler + Send, Handler::Handler: Send, @@ -351,6 +381,7 @@ pub struct AdvancedSubscriber { _subscriber: Subscriber<()>, receiver: Receiver, _liveliness_subscriber: Option>, + _token: Option, } #[zenoh_macros::unstable] @@ -501,12 +532,16 @@ impl Timed for PeriodicQuery { #[zenoh_macros::unstable] impl AdvancedSubscriber { - fn new(conf: AdvancedSubscriberBuilder<'_, '_, H>) -> ZResult + fn new(conf: AdvancedSubscriberBuilder<'_, '_, '_, H>) -> ZResult where H: IntoHandler + Send, { let (callback, receiver) = conf.handler.into_handler(); let key_expr = conf.key_expr?; + let meta = match conf.meta_key_expr { + Some(meta) => Some(meta?), + None => None, + }; let retransmission = conf.retransmission; let query_target = conf.query_target; let query_timeout = conf.query_timeout; @@ -817,11 +852,31 @@ impl AdvancedSubscriber { None }; + let token = if conf.liveliness { + let prefix = KE_PREFIX + / &subscriber.id().zid().into_keyexpr() + / &KeyExpr::try_from(subscriber.id().eid().to_string()).unwrap(); + let prefix = match meta { + Some(meta) => prefix / &meta / KE_SEPARATOR, + // We need this empty chunk because af a routing matching bug + _ => prefix / KE_EMPTY / KE_SEPARATOR, + }; + Some( + conf.session + .liveliness() + .declare_token(prefix / &key_expr) + .wait()?, + ) + } else { + None + }; + let reliable_subscriber = AdvancedSubscriber { statesref, _subscriber: subscriber, receiver, _liveliness_subscriber: liveliness_subscriber, + _token: token, }; Ok(reliable_subscriber) diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index d055312227..8e29672539 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -125,20 +125,20 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { } /// Some extensions to the [`zenoh::subscriber::SubscriberBuilder`](zenoh::pubsub::SubscriberBuilder) -pub trait DataSubscriberBuilderExt<'a, 'b, Handler> { +pub trait DataSubscriberBuilderExt<'a, 'b, 'c, Handler> { /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that enable caching. - fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; /// Ask for retransmission of detected lost Samples. /// /// Retransmission can only be achieved by Publishers that enable /// caching and sample_miss_detection. - fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; /// Turn this `Subscriber`into an `AdvancedSubscriber`. - fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler>; + fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; } impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilder<'a, 'b, Handler> { @@ -245,13 +245,13 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilde } } -impl<'a, 'b, Handler> DataSubscriberBuilderExt<'a, 'b, Handler> +impl<'a, 'b, 'c, Handler> DataSubscriberBuilderExt<'a, 'b, 'c, Handler> for SubscriberBuilder<'a, 'b, Handler> { /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that enable caching. - fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) .history(config) } @@ -260,13 +260,13 @@ impl<'a, 'b, Handler> DataSubscriberBuilderExt<'a, 'b, Handler> /// /// Retransmission can only be achieved by Publishers that enable /// caching and sample_miss_detection. - fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) .recovery(conf) } /// Turn this `Subscriber`into an `AdvancedSubscriber`. - fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, Handler> { + fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) } } From 54dc83cecdc4bb9dff1ca79bdccfd03bf78b668a Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 4 Dec 2024 16:58:24 +0100 Subject: [PATCH 46/88] Renaming --- zenoh-ext/examples/examples/z_advanced_pub.rs | 2 +- zenoh-ext/examples/examples/z_advanced_sub.rs | 3 ++- zenoh-ext/src/advanced_publisher.rs | 4 ++-- zenoh-ext/src/advanced_subscriber.rs | 8 ++++---- zenoh-ext/src/publisher_ext.rs | 6 +++--- zenoh-ext/src/subscriber_ext.rs | 9 +++++++++ zenoh-ext/tests/advanced.rs | 4 ++-- 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/zenoh-ext/examples/examples/z_advanced_pub.rs b/zenoh-ext/examples/examples/z_advanced_pub.rs index cd6f5eac08..266bec5f7b 100644 --- a/zenoh-ext/examples/examples/z_advanced_pub.rs +++ b/zenoh-ext/examples/examples/z_advanced_pub.rs @@ -34,7 +34,7 @@ async fn main() { .declare_publisher(&key_expr) .cache(CacheConfig::default().max_samples(history)) .sample_miss_detection() - .late_joiner_detection() + .publisher_detection() .await .unwrap(); diff --git a/zenoh-ext/examples/examples/z_advanced_sub.rs b/zenoh-ext/examples/examples/z_advanced_sub.rs index 9ced690d3f..626ba10e58 100644 --- a/zenoh-ext/examples/examples/z_advanced_sub.rs +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -31,8 +31,9 @@ async fn main() { println!("Declaring AdvancedSubscriber on {}", key_expr); let subscriber = session .declare_subscriber(key_expr) - .history(HistoryConfig::default().late_joiner()) + .history(HistoryConfig::default().detect_late_publishers()) .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1)))) + .subscriber_detection() .await .unwrap(); diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 21d4c4bfb0..c2d08e9d63 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -89,7 +89,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - pub fn late_joiner_detection(mut self) -> Self { + pub fn publisher_detection(mut self) -> Self { self.liveliness = true; self } @@ -97,7 +97,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { /// A key expression added to the liveliness token key expression /// and to the cache queryable key expression. /// It can be used to convey meta data. - pub fn meta_keyexpr(mut self, meta: TryIntoKeyExpr) -> Self + pub fn publisher_detection_metadata(mut self, meta: TryIntoKeyExpr) -> Self where TryIntoKeyExpr: TryInto>, >>::Error: Into, diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 2807192aaf..cfe38d198c 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -59,11 +59,11 @@ pub struct HistoryConfig { impl HistoryConfig { /// Enable detection of late joiner publishers and query for their historical data. /// - /// Let joiner detection can only be achieved for Publishers that enable late_joiner_detection. + /// Let joiner detection can only be achieved for Publishers that enable publisher_detection. /// History can only be retransmitted by Publishers that enable caching. #[zenoh_macros::unstable] #[inline] - pub fn late_joiner(mut self) -> Self { + pub fn detect_late_publishers(mut self) -> Self { self.liveliness = true; self } @@ -258,14 +258,14 @@ impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { } /// Allow this subscriber to be detected through liveliness. - pub fn late_joiner_detection(mut self) -> Self { + pub fn subscriber_detection(mut self) -> Self { self.liveliness = true; self } /// A key expression added to the liveliness token key expression. /// It can be used to convey meta data. - pub fn meta_keyexpr(mut self, meta: TryIntoKeyExpr) -> Self + pub fn subscriber_detection_metadata(mut self, meta: TryIntoKeyExpr) -> Self where TryIntoKeyExpr: TryInto>, >>::Error: Into, diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index 3555389daa..63922d7d0f 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -32,7 +32,7 @@ pub trait PublisherBuilderExt<'a, 'b, 'c> { /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; + fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; } impl<'a, 'b, 'c> PublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { @@ -54,7 +54,7 @@ impl<'a, 'b, 'c> PublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. - fn late_joiner_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { - AdvancedPublisherBuilder::new(self.session, self.key_expr).late_joiner_detection() + fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { + AdvancedPublisherBuilder::new(self.session, self.key_expr).publisher_detection() } } diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 8e29672539..433ece25c7 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -137,6 +137,9 @@ pub trait DataSubscriberBuilderExt<'a, 'b, 'c, Handler> { /// caching and sample_miss_detection. fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; + /// Allow this subscriber to be detected through liveliness. + fn subscriber_detection(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; + /// Turn this `Subscriber`into an `AdvancedSubscriber`. fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; } @@ -265,6 +268,12 @@ impl<'a, 'b, 'c, Handler> DataSubscriberBuilderExt<'a, 'b, 'c, Handler> .recovery(conf) } + /// Allow this subscriber to be detected through liveliness. + fn subscriber_detection(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { + AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) + .subscriber_detection() + } + /// Turn this `Subscriber`into an `AdvancedSubscriber`. fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index 7933350e46..f916a5681b 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -653,14 +653,14 @@ async fn test_advanced_late_joiner() { let sub = ztimeout!(peer2 .declare_subscriber(ADVANCED_LATE_JOINER_KEYEXPR) - .history(HistoryConfig::default().late_joiner())) + .history(HistoryConfig::default().detect_late_publishers())) .unwrap(); tokio::time::sleep(SLEEP).await; let publ = ztimeout!(peer1 .declare_publisher(ADVANCED_LATE_JOINER_KEYEXPR) .cache(CacheConfig::default().max_samples(10)) - .late_joiner_detection()) + .publisher_detection()) .unwrap(); ztimeout!(publ.put("1")).unwrap(); ztimeout!(publ.put("2")).unwrap(); From 9d180857c76854b807d6b2791447eee64135fb48 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 4 Dec 2024 17:53:24 +0100 Subject: [PATCH 47/88] Fix compilation issues --- zenoh-ext/src/advanced_publisher.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index d0e29a3fa4..cd106466de 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -297,9 +297,9 @@ impl<'a> AdvancedPublisher<'a> { delete } - /// Return the [`MatchingStatus`](zenoh::pubsub::MatchingStatus) of the publisher. + /// Return the [`MatchingStatus`](zenoh::matching::MatchingStatus) of the publisher. /// - /// [`MatchingStatus::matching_subscribers`](zenoh::pubsub::MatchingStatus::matching_subscribers) + /// [`MatchingStatus::matching`](zenoh::matching::MatchingStatus::matching) /// will return true if there exist Subscribers matching the Publisher's key expression and false otherwise. /// /// # Examples @@ -313,7 +313,7 @@ impl<'a> AdvancedPublisher<'a> { /// .matching_status() /// .await /// .unwrap() - /// .matching_subscribers(); + /// .matching(); /// # } /// ``` #[zenoh_macros::unstable] @@ -321,10 +321,10 @@ impl<'a> AdvancedPublisher<'a> { self.publisher.matching_status() } - /// Return a [`MatchingListener`](zenoh::pubsub::MatchingStatus) for this Publisher. + /// Return a [`MatchingListener`](zenoh::matching::MatchingStatus) for this Publisher. /// - /// The [`MatchingListener`](zenoh::pubsub::MatchingStatus) that will send a notification each time - /// the [`MatchingStatus`](zenoh::pubsub::MatchingStatus) of the Publisher changes. + /// The [`MatchingListener`](zenoh::matching::MatchingStatus) that will send a notification each time + /// the [`MatchingStatus`](zenoh::matching::MatchingStatus) of the Publisher changes. /// /// # Examples /// ```no_run @@ -335,7 +335,7 @@ impl<'a> AdvancedPublisher<'a> { /// let publisher = session.declare_publisher("key/expression").await.unwrap(); /// let matching_listener = publisher.matching_listener().await.unwrap(); /// while let Ok(matching_status) = matching_listener.recv_async().await { - /// if matching_status.matching_subscribers() { + /// if matching_status.matching() { /// println!("Publisher has matching subscribers."); /// } else { /// println!("Publisher has NO MORE matching subscribers."); From 7d3ab8d9bf40515bfaca42fac1a55735692d444a Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 10:32:57 +0100 Subject: [PATCH 48/88] Remove AdvancedCache from public API --- zenoh-ext/src/advanced_cache.rs | 37 ++--------------------------- zenoh-ext/src/advanced_publisher.rs | 8 +++---- zenoh-ext/src/lib.rs | 2 +- zenoh-ext/src/session_ext.rs | 20 ---------------- 4 files changed, 6 insertions(+), 61 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 4a33f32add..47fffa6649 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -22,7 +22,7 @@ use futures::{select, FutureExt}; use tokio::task; use zenoh::{ handlers::FifoChannelHandler, - internal::{bail, ResolveFuture}, + internal::bail, key_expr::{ format::{ke, kedefine}, keyexpr, KeyExpr, OwnedKeyExpr, @@ -31,7 +31,7 @@ use zenoh::{ pubsub::Subscriber, query::{Query, Queryable, ZenohParameters}, sample::{Locality, Sample}, - Resolvable, Resolve, Result as ZResult, Session, Wait, + Resolvable, Result as ZResult, Session, Wait, }; // #[zenoh_macros::unstable] @@ -128,24 +128,11 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { self } - /// Restrict the matching queries that will be receive by this [`AdvancedCache`]'s queryable - /// to the ones that have the given [`Locality`](zenoh::sample::Locality). - #[inline] - pub fn queryable_allowed_origin(mut self, origin: Locality) -> Self { - self.queryable_origin = origin; - self - } - /// Change the history size for each resource. pub fn history(mut self, history: CacheConfig) -> Self { self.history = history; self } - - pub fn liveliness(mut self, enabled: bool) -> Self { - self.liveliness = enabled; - self - } } impl Resolvable for AdvancedCacheBuilder<'_, '_, '_> { @@ -358,24 +345,4 @@ impl AdvancedCache { _stoptx: stoptx, }) } - - /// Close this AdvancedCache - #[inline] - pub fn close(self) -> impl Resolve> { - ResolveFuture::new(async move { - let AdvancedCache { - _queryable, - _sub, - _token, - _stoptx, - } = self; - _sub.undeclare().await?; - if let Some(token) = _token { - token.undeclare().await?; - } - _queryable.undeclare().await?; - drop(_stoptx); - Ok(()) - }) - } } diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index cd106466de..742449382e 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -28,9 +28,8 @@ use zenoh::{ Resolvable, Resolve, Result as ZResult, Session, Wait, }; -use crate::{ - advanced_cache::{AdvancedCache, CacheConfig, KE_EMPTY, KE_PREFIX, KE_SEPARATOR, KE_UHLC}, - SessionExt, +use crate::advanced_cache::{ + AdvancedCache, AdvancedCacheBuilder, CacheConfig, KE_EMPTY, KE_PREFIX, KE_SEPARATOR, KE_UHLC, }; #[derive(PartialEq)] @@ -176,8 +175,7 @@ impl<'a> AdvancedPublisher<'a> { let cache = if conf.cache { Some( - conf.session - .declare_advanced_cache(key_expr.clone().into_owned()) + AdvancedCacheBuilder::new(conf.session, Ok(key_expr.clone().into_owned())) .subscriber_allowed_origin(Locality::SessionLocal) .history(conf.history) .queryable_prefix(&prefix) diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index c7e8701e30..030c4937f7 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -39,7 +39,7 @@ pub use crate::serialization::{ }; #[cfg(feature = "unstable")] pub use crate::{ - advanced_cache::{AdvancedCache, AdvancedCacheBuilder, CacheConfig}, + advanced_cache::CacheConfig, advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder, Sequencing}, advanced_subscriber::{ AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, RecoveryConfig, diff --git a/zenoh-ext/src/session_ext.rs b/zenoh-ext/src/session_ext.rs index 8c24ccc9e2..9d3c430aaf 100644 --- a/zenoh-ext/src/session_ext.rs +++ b/zenoh-ext/src/session_ext.rs @@ -15,7 +15,6 @@ use zenoh::{key_expr::KeyExpr, session::Session, Error}; use super::PublicationCacheBuilder; -use crate::advanced_cache::AdvancedCacheBuilder; /// Some extensions to the [`zenoh::Session`](zenoh::Session) #[zenoh_macros::unstable] @@ -45,14 +44,6 @@ pub trait SessionExt { where TryIntoKeyExpr: TryInto>, >>::Error: Into; - - fn declare_advanced_cache<'a, 'b, 'c, TryIntoKeyExpr>( - &'a self, - pub_key_expr: TryIntoKeyExpr, - ) -> AdvancedCacheBuilder<'a, 'b, 'c> - where - TryIntoKeyExpr: TryInto>, - >>::Error: Into; } impl SessionExt for Session { @@ -66,15 +57,4 @@ impl SessionExt for Session { { PublicationCacheBuilder::new(self, pub_key_expr.try_into().map_err(Into::into)) } - - fn declare_advanced_cache<'a, 'b, 'c, TryIntoKeyExpr>( - &'a self, - pub_key_expr: TryIntoKeyExpr, - ) -> AdvancedCacheBuilder<'a, 'b, 'c> - where - TryIntoKeyExpr: TryInto>, - >>::Error: Into, - { - AdvancedCacheBuilder::new(self, pub_key_expr.try_into().map_err(Into::into)) - } } From e59e76b9164c4813bfc5dfc6c099637670c07ffa Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 11:36:59 +0100 Subject: [PATCH 49/88] Update Session admin to match AdvancedSub --- zenoh/src/api/admin.rs | 56 ++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/zenoh/src/api/admin.rs b/zenoh/src/api/admin.rs index e18703bf27..a9ffc3a8fd 100644 --- a/zenoh/src/api/admin.rs +++ b/zenoh/src/api/admin.rs @@ -40,37 +40,51 @@ use crate::{ handlers::Callback, }; -static KE_STARSTAR: &keyexpr = ke!("**"); static KE_PREFIX: &keyexpr = ke!("@"); +static KE_ADV_PREFIX: &keyexpr = ke!("@adv"); +static KE_EMPTY: &keyexpr = ke!("_"); +static KE_STARSTAR: &keyexpr = ke!("**"); static KE_SESSION: &keyexpr = ke!("session"); static KE_TRANSPORT_UNICAST: &keyexpr = ke!("transport/unicast"); static KE_LINK: &keyexpr = ke!("link"); pub(crate) fn init(session: WeakSession) { if let Ok(own_zid) = keyexpr::new(&session.zid().to_string()) { - let admin_key = KeyExpr::from(KE_PREFIX / own_zid / KE_SESSION / KE_STARSTAR); - let _admin_qabl = session.declare_queryable_inner( - &admin_key, + &KeyExpr::from(KE_PREFIX / own_zid / KE_SESSION / KE_STARSTAR), + true, + Locality::SessionLocal, + Callback::new(Arc::new({ + let session = session.clone(); + move |q| on_admin_query(&session, KE_PREFIX, q) + })), + ); + + let adv_prefix = KE_ADV_PREFIX / own_zid / KE_EMPTY / KE_EMPTY / KE_PREFIX / KE_PREFIX; + + let _admin_adv_qabl = session.declare_queryable_inner( + &KeyExpr::from(&adv_prefix / own_zid / KE_SESSION / KE_STARSTAR), true, Locality::SessionLocal, Callback::new(Arc::new({ let session = session.clone(); - move |q| on_admin_query(&session, q) + move |q| on_admin_query(&session, &adv_prefix, q) })), ); } } -pub(crate) fn on_admin_query(session: &WeakSession, query: Query) { - fn reply_peer(own_zid: &keyexpr, query: &Query, peer: TransportPeer) { +pub(crate) fn on_admin_query(session: &WeakSession, prefix: &keyexpr, query: Query) { + fn reply_peer(prefix: &keyexpr, own_zid: &keyexpr, query: &Query, peer: TransportPeer) { let zid = peer.zid.to_string(); if let Ok(zid) = keyexpr::new(&zid) { - let key_expr = KE_PREFIX / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid; + let key_expr = prefix / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid; if query.key_expr().intersects(&key_expr) { match serde_json::to_vec(&peer) { Ok(bytes) => { - let _ = query.reply(key_expr, bytes).wait(); + let reply_expr = + KE_PREFIX / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid; + let _ = query.reply(reply_expr, bytes).wait(); } Err(e) => tracing::debug!("Admin query error: {}", e), } @@ -80,17 +94,19 @@ pub(crate) fn on_admin_query(session: &WeakSession, query: Query) { let mut s = DefaultHasher::new(); link.hash(&mut s); if let Ok(lid) = keyexpr::new(&s.finish().to_string()) { - let key_expr = KE_PREFIX - / own_zid - / KE_SESSION - / KE_TRANSPORT_UNICAST - / zid - / KE_LINK - / lid; + let key_expr = + prefix / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid / KE_LINK / lid; if query.key_expr().intersects(&key_expr) { match serde_json::to_vec(&link) { Ok(bytes) => { - let _ = query.reply(key_expr, bytes).wait(); + let reply_expr = KE_PREFIX + / own_zid + / KE_SESSION + / KE_TRANSPORT_UNICAST + / zid + / KE_LINK + / lid; + let _ = query.reply(reply_expr, bytes).wait(); } Err(e) => tracing::debug!("Admin query error: {}", e), } @@ -100,19 +116,21 @@ pub(crate) fn on_admin_query(session: &WeakSession, query: Query) { } } + println!("on admin query"); + if let Ok(own_zid) = keyexpr::new(&session.zid().to_string()) { for transport in zenoh_runtime::ZRuntime::Net .block_in_place(session.runtime.manager().get_transports_unicast()) { if let Ok(peer) = transport.get_peer() { - reply_peer(own_zid, &query, peer); + reply_peer(prefix, own_zid, &query, peer); } } for transport in zenoh_runtime::ZRuntime::Net .block_in_place(session.runtime.manager().get_transports_multicast()) { for peer in transport.get_peers().unwrap_or_default() { - reply_peer(own_zid, &query, peer); + reply_peer(prefix, own_zid, &query, peer); } } } From 71ce64bf1ae149a7fba62d3f2d90d237da1d50a9 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 12:10:52 +0100 Subject: [PATCH 50/88] Gather constants --- zenoh-ext/src/advanced_cache.rs | 15 ++----------- zenoh-ext/src/advanced_publisher.rs | 12 +++++----- zenoh-ext/src/advanced_subscriber.rs | 24 ++++++++++---------- zenoh/src/api/admin.rs | 33 ++++++++++++++++++---------- zenoh/src/lib.rs | 9 ++++++++ 5 files changed, 49 insertions(+), 44 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 47fffa6649..a7d2e8bc49 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -31,22 +31,11 @@ use zenoh::{ pubsub::Subscriber, query::{Query, Queryable, ZenohParameters}, sample::{Locality, Sample}, - Resolvable, Result as ZResult, Session, Wait, + Resolvable, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_STARSTAR, }; -// #[zenoh_macros::unstable] -// pub(crate) static KE_STAR: &keyexpr = ke!("*"); -#[zenoh_macros::unstable] -pub(crate) static KE_STARSTAR: &keyexpr = ke!("**"); -#[zenoh_macros::unstable] -pub(crate) static KE_PREFIX: &keyexpr = ke!("@adv"); -#[zenoh_macros::unstable] -pub(crate) static KE_SEPARATOR: &keyexpr = ke!("@"); -#[zenoh_macros::unstable] pub(crate) static KE_UHLC: &keyexpr = ke!("uhlc"); #[zenoh_macros::unstable] -pub(crate) static KE_EMPTY: &keyexpr = ke!("_"); -#[zenoh_macros::unstable] kedefine!( pub(crate) ke_liveliness: "@adv/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", ); @@ -102,7 +91,7 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { AdvancedCacheBuilder { session, pub_key_expr, - queryable_prefix: Some(Ok((KE_PREFIX / KE_STARSTAR / KE_SEPARATOR).into())), + queryable_prefix: Some(Ok((KE_ADV_PREFIX / KE_STARSTAR / KE_AT).into())), subscriber_origin: Locality::default(), queryable_origin: Locality::default(), history: CacheConfig::default(), diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 742449382e..3993f28a61 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -25,12 +25,10 @@ use zenoh::{ qos::{CongestionControl, Priority}, sample::{Locality, SourceInfo}, session::EntityGlobalId, - Resolvable, Resolve, Result as ZResult, Session, Wait, + Resolvable, Resolve, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_EMPTY, }; -use crate::advanced_cache::{ - AdvancedCache, AdvancedCacheBuilder, CacheConfig, KE_EMPTY, KE_PREFIX, KE_SEPARATOR, KE_UHLC, -}; +use crate::advanced_cache::{AdvancedCache, AdvancedCacheBuilder, CacheConfig, KE_UHLC}; #[derive(PartialEq)] pub enum Sequencing { @@ -145,7 +143,7 @@ impl<'a> AdvancedPublisher<'a> { .declare_publisher(key_expr.clone().into_owned()) .wait()?; let id = publisher.id(); - let prefix = KE_PREFIX / &id.zid().into_keyexpr(); + let prefix = KE_ADV_PREFIX / &id.zid().into_keyexpr(); let prefix = match conf.sequencing { Sequencing::SequenceNumber => { prefix / &KeyExpr::try_from(id.eid().to_string()).unwrap() @@ -153,9 +151,9 @@ impl<'a> AdvancedPublisher<'a> { _ => prefix / KE_UHLC, }; let prefix = match meta { - Some(meta) => prefix / &meta / KE_SEPARATOR, + Some(meta) => prefix / &meta / KE_AT, // We need this empty chunk because af a routing matching bug - _ => prefix / KE_EMPTY / KE_SEPARATOR, + _ => prefix / KE_EMPTY / KE_AT, }; let seqnum = match conf.sequencing { diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index cfe38d198c..46c55134be 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -23,7 +23,7 @@ use zenoh::{ }, sample::{Locality, Sample, SampleKind}, session::{EntityGlobalId, EntityId}, - Resolvable, Resolve, Session, Wait, + Resolvable, Resolve, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_EMPTY, KE_STARSTAR, }; use zenoh_util::{Timed, TimedEvent, Timer}; #[zenoh_macros::unstable] @@ -44,9 +44,7 @@ use { zenoh::Result as ZResult, }; -use crate::advanced_cache::{ - ke_liveliness, KE_EMPTY, KE_PREFIX, KE_SEPARATOR, KE_STARSTAR, KE_UHLC, -}; +use crate::advanced_cache::{ke_liveliness, KE_UHLC}; #[derive(Debug, Default, Clone)] /// Configure query for historical data. @@ -492,11 +490,11 @@ impl Timed for PeriodicQuery { let states = &mut *lock; if let Some(state) = states.sequenced_states.get_mut(&self.source_id) { state.pending_queries += 1; - let query_expr = KE_PREFIX + let query_expr = KE_ADV_PREFIX / &self.source_id.zid().into_keyexpr() / &KeyExpr::try_from(self.source_id.eid().to_string()).unwrap() / KE_STARSTAR - / KE_SEPARATOR + / KE_AT / &states.key_expr; let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); @@ -588,11 +586,11 @@ impl AdvancedSubscriber { && !state.pending_samples.is_empty() { state.pending_queries += 1; - let query_expr = KE_PREFIX + let query_expr = KE_ADV_PREFIX / &source_id.zid().into_keyexpr() / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() / KE_STARSTAR - / KE_SEPARATOR + / KE_AT / &key_expr; let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); @@ -649,7 +647,7 @@ impl AdvancedSubscriber { let _ = conf .session .get(Selector::from(( - KE_PREFIX / KE_STARSTAR / KE_SEPARATOR / &key_expr, + KE_ADV_PREFIX / KE_STARSTAR / KE_AT / &key_expr, params, ))) .callback({ @@ -839,7 +837,7 @@ impl AdvancedSubscriber { conf .session .liveliness() - .declare_subscriber(KE_PREFIX / KE_STARSTAR / KE_SEPARATOR / &key_expr) + .declare_subscriber(KE_ADV_PREFIX / KE_STARSTAR / KE_AT / &key_expr) // .declare_subscriber(keformat!(ke_liveliness_all::formatter(), zid = 0, eid = 0, remaining = key_expr).unwrap()) .history(true) .callback(live_callback) @@ -853,13 +851,13 @@ impl AdvancedSubscriber { }; let token = if conf.liveliness { - let prefix = KE_PREFIX + let prefix = KE_ADV_PREFIX / &subscriber.id().zid().into_keyexpr() / &KeyExpr::try_from(subscriber.id().eid().to_string()).unwrap(); let prefix = match meta { - Some(meta) => prefix / &meta / KE_SEPARATOR, + Some(meta) => prefix / &meta / KE_AT, // We need this empty chunk because af a routing matching bug - _ => prefix / KE_EMPTY / KE_SEPARATOR, + _ => prefix / KE_EMPTY / KE_AT, }; Some( conf.session diff --git a/zenoh/src/api/admin.rs b/zenoh/src/api/admin.rs index a9ffc3a8fd..b0de76ccf2 100644 --- a/zenoh/src/api/admin.rs +++ b/zenoh/src/api/admin.rs @@ -40,10 +40,23 @@ use crate::{ handlers::Callback, }; -static KE_PREFIX: &keyexpr = ke!("@"); +#[cfg(feature = "internal")] +pub static KE_AT: &keyexpr = ke!("@"); +#[cfg(not(feature = "internal"))] +static KE_AT: &keyexpr = ke!("@"); +#[cfg(feature = "internal")] +pub static KE_ADV_PREFIX: &keyexpr = ke!("@adv"); +#[cfg(not(feature = "internal"))] static KE_ADV_PREFIX: &keyexpr = ke!("@adv"); +#[cfg(feature = "internal")] +pub static KE_EMPTY: &keyexpr = ke!("_"); +#[cfg(not(feature = "internal"))] static KE_EMPTY: &keyexpr = ke!("_"); +#[cfg(feature = "internal")] +pub static KE_STARSTAR: &keyexpr = ke!("**"); +#[cfg(not(feature = "internal"))] static KE_STARSTAR: &keyexpr = ke!("**"); + static KE_SESSION: &keyexpr = ke!("session"); static KE_TRANSPORT_UNICAST: &keyexpr = ke!("transport/unicast"); static KE_LINK: &keyexpr = ke!("link"); @@ -51,16 +64,16 @@ static KE_LINK: &keyexpr = ke!("link"); pub(crate) fn init(session: WeakSession) { if let Ok(own_zid) = keyexpr::new(&session.zid().to_string()) { let _admin_qabl = session.declare_queryable_inner( - &KeyExpr::from(KE_PREFIX / own_zid / KE_SESSION / KE_STARSTAR), + &KeyExpr::from(KE_AT / own_zid / KE_SESSION / KE_STARSTAR), true, Locality::SessionLocal, Callback::new(Arc::new({ let session = session.clone(); - move |q| on_admin_query(&session, KE_PREFIX, q) + move |q| on_admin_query(&session, KE_AT, q) })), ); - let adv_prefix = KE_ADV_PREFIX / own_zid / KE_EMPTY / KE_EMPTY / KE_PREFIX / KE_PREFIX; + let adv_prefix = KE_ADV_PREFIX / own_zid / KE_EMPTY / KE_EMPTY / KE_AT / KE_AT; let _admin_adv_qabl = session.declare_queryable_inner( &KeyExpr::from(&adv_prefix / own_zid / KE_SESSION / KE_STARSTAR), @@ -82,8 +95,7 @@ pub(crate) fn on_admin_query(session: &WeakSession, prefix: &keyexpr, query: Que if query.key_expr().intersects(&key_expr) { match serde_json::to_vec(&peer) { Ok(bytes) => { - let reply_expr = - KE_PREFIX / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid; + let reply_expr = KE_AT / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid; let _ = query.reply(reply_expr, bytes).wait(); } Err(e) => tracing::debug!("Admin query error: {}", e), @@ -99,7 +111,7 @@ pub(crate) fn on_admin_query(session: &WeakSession, prefix: &keyexpr, query: Que if query.key_expr().intersects(&key_expr) { match serde_json::to_vec(&link) { Ok(bytes) => { - let reply_expr = KE_PREFIX + let reply_expr = KE_AT / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST @@ -171,10 +183,9 @@ impl TransportMulticastEventHandler for Handler { ) -> ZResult> { if let Ok(own_zid) = keyexpr::new(&self.session.zid().to_string()) { if let Ok(zid) = keyexpr::new(&peer.zid.to_string()) { - let expr = WireExpr::from( - &(KE_PREFIX / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid), - ) - .to_owned(); + let expr = + WireExpr::from(&(KE_AT / own_zid / KE_SESSION / KE_TRANSPORT_UNICAST / zid)) + .to_owned(); let info = DataInfo { encoding: Some(Encoding::APPLICATION_JSON), ..Default::default() diff --git a/zenoh/src/lib.rs b/zenoh/src/lib.rs index 6f231776e5..1341f16ac2 100644 --- a/zenoh/src/lib.rs +++ b/zenoh/src/lib.rs @@ -81,6 +81,15 @@ extern crate zenoh_result; mod api; mod net; +#[cfg(feature = "internal")] +pub use api::admin::KE_ADV_PREFIX; +#[cfg(feature = "internal")] +pub use api::admin::KE_AT; +#[cfg(feature = "internal")] +pub use api::admin::KE_EMPTY; +#[cfg(feature = "internal")] +pub use api::admin::KE_STARSTAR; + lazy_static::lazy_static!( static ref LONG_VERSION: String = format!("{} built with {}", GIT_VERSION, env!("RUSTC_VERSION")); ); From 8810b374048c33662282794ce20f0e3fdb74a07e Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 12:14:48 +0100 Subject: [PATCH 51/88] Fix doc build --- zenoh-ext/src/advanced_cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index a7d2e8bc49..4c7943f38e 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -41,7 +41,7 @@ kedefine!( ); #[derive(Debug, Clone)] -/// Configure an [`AdvancedCache`]. +/// Configure an [`AdvancedPublisher`](crate::AdvancedPublisher) cache. pub struct CacheConfig { sample_depth: usize, resources_limit: Option, From dba1d5055206398b73692b2b02b142d22d2d1dc0 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 12:58:22 +0100 Subject: [PATCH 52/88] Renaming --- zenoh-ext/examples/examples/z_advanced_pub.rs | 2 +- zenoh-ext/examples/examples/z_advanced_sub.rs | 2 +- zenoh-ext/src/lib.rs | 4 ++-- zenoh-ext/src/publisher_ext.rs | 4 ++-- zenoh-ext/src/subscriber_ext.rs | 4 ++-- zenoh-ext/tests/advanced.rs | 3 ++- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/zenoh-ext/examples/examples/z_advanced_pub.rs b/zenoh-ext/examples/examples/z_advanced_pub.rs index 266bec5f7b..6437515c8f 100644 --- a/zenoh-ext/examples/examples/z_advanced_pub.rs +++ b/zenoh-ext/examples/examples/z_advanced_pub.rs @@ -16,7 +16,7 @@ use std::time::Duration; use clap::{arg, Parser}; use zenoh::{config::Config, key_expr::KeyExpr}; use zenoh_config::ModeDependentValue; -use zenoh_ext::{CacheConfig, PublisherBuilderExt}; +use zenoh_ext::{AdvancedPublisherBuilderExt, CacheConfig}; use zenoh_ext_examples::CommonArgs; #[tokio::main] diff --git a/zenoh-ext/examples/examples/z_advanced_sub.rs b/zenoh-ext/examples/examples/z_advanced_sub.rs index 626ba10e58..5bea70f7d5 100644 --- a/zenoh-ext/examples/examples/z_advanced_sub.rs +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -15,7 +15,7 @@ use std::time::Duration; use clap::{arg, Parser}; use zenoh::config::Config; -use zenoh_ext::{DataSubscriberBuilderExt, HistoryConfig, RecoveryConfig}; +use zenoh_ext::{AdvancedSubscriberBuilderExt, HistoryConfig, RecoveryConfig}; use zenoh_ext_examples::CommonArgs; #[tokio::main] diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 030c4937f7..4d3ce365ea 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -45,11 +45,11 @@ pub use crate::{ AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, RecoveryConfig, }, publication_cache::{PublicationCache, PublicationCacheBuilder}, - publisher_ext::PublisherBuilderExt, + publisher_ext::AdvancedPublisherBuilderExt, querying_subscriber::{ ExtractSample, FetchingSubscriber, FetchingSubscriberBuilder, KeySpace, LivelinessSpace, QueryingSubscriberBuilder, UserSpace, }, session_ext::SessionExt, - subscriber_ext::{DataSubscriberBuilderExt, SubscriberBuilderExt, SubscriberForward}, + subscriber_ext::{AdvancedSubscriberBuilderExt, SubscriberBuilderExt, SubscriberForward}, }; diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index 63922d7d0f..faa01fe124 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -17,7 +17,7 @@ use crate::{advanced_cache::CacheConfig, AdvancedPublisherBuilder}; /// Some extensions to the [`zenoh::publication::PublisherBuilder`](zenoh::publication::PublisherBuilder) #[zenoh_macros::unstable] -pub trait PublisherBuilderExt<'a, 'b, 'c> { +pub trait AdvancedPublisherBuilderExt<'a, 'b, 'c> { /// Allow matching Subscribers to detect lost samples and /// optionally ask for retransimission. /// @@ -35,7 +35,7 @@ pub trait PublisherBuilderExt<'a, 'b, 'c> { fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; } -impl<'a, 'b, 'c> PublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { +impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { /// Allow matching Subscribers to detect lost samples and /// optionally ask for retransimission. /// diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 433ece25c7..deecce4830 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -125,7 +125,7 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { } /// Some extensions to the [`zenoh::subscriber::SubscriberBuilder`](zenoh::pubsub::SubscriberBuilder) -pub trait DataSubscriberBuilderExt<'a, 'b, 'c, Handler> { +pub trait AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> { /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that enable caching. @@ -248,7 +248,7 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilde } } -impl<'a, 'b, 'c, Handler> DataSubscriberBuilderExt<'a, 'b, 'c, Handler> +impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> for SubscriberBuilder<'a, 'b, Handler> { /// Enable query for historical data. diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs index f916a5681b..f7d3593c63 100644 --- a/zenoh-ext/tests/advanced.rs +++ b/zenoh-ext/tests/advanced.rs @@ -15,7 +15,8 @@ use zenoh::sample::SampleKind; use zenoh_config::{EndPoint, ModeDependentValue, WhatAmI}; use zenoh_ext::{ - CacheConfig, DataSubscriberBuilderExt, HistoryConfig, PublisherBuilderExt, RecoveryConfig, + AdvancedPublisherBuilderExt, AdvancedSubscriberBuilderExt, CacheConfig, HistoryConfig, + RecoveryConfig, }; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] From 95ce535668ff7e930691a8eb8ec19e9403ae8d1f Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 13:18:05 +0100 Subject: [PATCH 53/88] Mark PublicationCache and QueryingSubscriber as deprecated and remove related examples --- zenoh-ext/examples/Cargo.toml | 8 -- zenoh-ext/examples/examples/z_pub_cache.rs | 94 ---------------------- zenoh-ext/examples/examples/z_query_sub.rs | 80 ------------------ zenoh-ext/src/lib.rs | 1 + zenoh-ext/src/publication_cache.rs | 19 +++++ zenoh-ext/src/querying_subscriber.rs | 69 ++++++++++++++++ zenoh-ext/src/session_ext.rs | 4 + zenoh-ext/src/subscriber_ext.rs | 7 ++ zenoh-ext/tests/liveliness.rs | 4 + 9 files changed, 104 insertions(+), 182 deletions(-) delete mode 100644 zenoh-ext/examples/examples/z_pub_cache.rs delete mode 100644 zenoh-ext/examples/examples/z_query_sub.rs diff --git a/zenoh-ext/examples/Cargo.toml b/zenoh-ext/examples/Cargo.toml index 658eba1065..10d09d4af1 100644 --- a/zenoh-ext/examples/Cargo.toml +++ b/zenoh-ext/examples/Cargo.toml @@ -41,14 +41,6 @@ zenoh-ext = { workspace = true, features = ["unstable"] } [dev-dependencies] zenoh-config = { workspace = true } -[[example]] -name = "z_query_sub" -path = "examples/z_query_sub.rs" - -[[example]] -name = "z_pub_cache" -path = "examples/z_pub_cache.rs" - [[example]] name = "z_member" path = "examples/z_member.rs" diff --git a/zenoh-ext/examples/examples/z_pub_cache.rs b/zenoh-ext/examples/examples/z_pub_cache.rs deleted file mode 100644 index d8e13faec4..0000000000 --- a/zenoh-ext/examples/examples/z_pub_cache.rs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// -use std::time::Duration; - -use clap::{arg, Parser}; -use zenoh::{config::Config, key_expr::KeyExpr}; -use zenoh_config::ModeDependentValue; -use zenoh_ext::*; -use zenoh_ext_examples::CommonArgs; - -#[tokio::main] -async fn main() { - // Initiate logging - zenoh::init_log_from_env_or("error"); - - let (config, key_expr, value, history, prefix, complete) = parse_args(); - - println!("Opening session..."); - let session = zenoh::open(config).await.unwrap(); - - println!("Declaring PublicationCache on {}", &key_expr); - let mut publication_cache_builder = session - .declare_publication_cache(&key_expr) - .history(history) - .queryable_complete(complete); - if let Some(prefix) = prefix { - publication_cache_builder = publication_cache_builder.queryable_prefix(prefix); - } - let _publication_cache = publication_cache_builder.await.unwrap(); - - println!("Press CTRL-C to quit..."); - for idx in 0..u32::MAX { - tokio::time::sleep(Duration::from_secs(1)).await; - let buf = format!("[{idx:4}] {value}"); - println!("Put Data ('{}': '{}')", &key_expr, buf); - session.put(&key_expr, buf).await.unwrap(); - } -} - -#[derive(clap::Parser, Clone, PartialEq, Eq, Hash, Debug)] -struct Args { - #[arg(short, long, default_value = "demo/example/zenoh-rs-pub")] - /// The key expression to publish. - key: KeyExpr<'static>, - #[arg(short, long, default_value = "Pub from Rust!")] - /// The value to reply to queries. - value: String, - #[arg(short = 'i', long, default_value = "1")] - /// The number of publications to keep in cache. - history: usize, - #[arg(short = 'o', long)] - /// Set `complete` option to true. This means that this queryable is ultimate data source, no need to scan other queryables. - complete: bool, - #[arg(short = 'x', long)] - /// An optional queryable prefix. - prefix: Option, - #[command(flatten)] - common: CommonArgs, -} - -fn parse_args() -> ( - Config, - KeyExpr<'static>, - String, - usize, - Option, - bool, -) { - let args = Args::parse(); - let mut config: Config = args.common.into(); - config - .timestamping - .set_enabled(Some(ModeDependentValue::Unique(true))) - .unwrap(); - ( - config, - args.key, - args.value, - args.history, - args.prefix, - args.complete, - ) -} diff --git a/zenoh-ext/examples/examples/z_query_sub.rs b/zenoh-ext/examples/examples/z_query_sub.rs deleted file mode 100644 index 2e8c832670..0000000000 --- a/zenoh-ext/examples/examples/z_query_sub.rs +++ /dev/null @@ -1,80 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// -use clap::{arg, Parser}; -use zenoh::{config::Config, query::ReplyKeyExpr}; -use zenoh_ext::*; -use zenoh_ext_examples::CommonArgs; - -#[tokio::main] -async fn main() { - // Initiate logging - zenoh::init_log_from_env_or("error"); - - let (config, key_expr, query) = parse_args(); - - println!("Opening session..."); - let session = zenoh::open(config).await.unwrap(); - - println!( - "Declaring QueryingSubscriber on {} with an initial query on {}", - key_expr, - query.as_ref().unwrap_or(&key_expr) - ); - let subscriber = if let Some(selector) = query { - session - .declare_subscriber(key_expr) - .querying() - .query_selector(&selector) - .query_accept_replies(ReplyKeyExpr::Any) - .await - .unwrap() - } else { - session - .declare_subscriber(key_expr) - .querying() - .await - .unwrap() - }; - - println!("Press CTRL-C to quit..."); - while let Ok(sample) = subscriber.recv_async().await { - let payload = sample - .payload() - .try_to_string() - .unwrap_or_else(|e| e.to_string().into()); - println!( - ">> [Subscriber] Received {} ('{}': '{}')", - sample.kind(), - sample.key_expr().as_str(), - payload - ); - } -} - -#[derive(clap::Parser, Clone, PartialEq, Eq, Hash, Debug)] -struct Args { - #[arg(short, long, default_value = "demo/example/**")] - /// The key expression to subscribe onto. - key: String, - #[arg(short, long)] - /// The selector to use for queries (by default it's same as 'key' option) - query: Option, - #[command(flatten)] - common: CommonArgs, -} - -fn parse_args() -> (Config, String, Option) { - let args = Args::parse(); - (args.common.into(), args.key, args.query) -} diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 4d3ce365ea..367aff98d3 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -38,6 +38,7 @@ pub use crate::serialization::{ ZReadIter, ZSerializer, }; #[cfg(feature = "unstable")] +#[allow(deprecated)] pub use crate::{ advanced_cache::CacheConfig, advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder, Sequencing}, diff --git a/zenoh-ext/src/publication_cache.rs b/zenoh-ext/src/publication_cache.rs index 403a887100..37989d5c8f 100644 --- a/zenoh-ext/src/publication_cache.rs +++ b/zenoh-ext/src/publication_cache.rs @@ -31,6 +31,7 @@ use zenoh::{ /// The builder of PublicationCache, allowing to configure it. #[zenoh_macros::unstable] #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct PublicationCacheBuilder<'a, 'b, 'c, const BACKGROUND: bool = false> { session: &'a Session, pub_key_expr: ZResult>, @@ -41,6 +42,7 @@ pub struct PublicationCacheBuilder<'a, 'b, 'c, const BACKGROUND: bool = false> { resources_limit: Option, } +#[allow(deprecated)] impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { pub(crate) fn new( session: &'a Session, @@ -58,6 +60,7 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { } /// Change the prefix used for queryable. + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn queryable_prefix(mut self, queryable_prefix: TryIntoKeyExpr) -> Self where TryIntoKeyExpr: TryInto>, @@ -71,29 +74,34 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { /// to the ones that have the given [`Locality`](zenoh::prelude::Locality). #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn queryable_allowed_origin(mut self, origin: Locality) -> Self { self.queryable_origin = Some(origin); self } /// Set completeness option for the queryable. + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn queryable_complete(mut self, complete: bool) -> Self { self.complete = Some(complete); self } /// Change the history size for each resource. + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn history(mut self, history: usize) -> Self { self.history = history; self } /// Change the limit number of cached resources. + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn resources_limit(mut self, limit: usize) -> Self { self.resources_limit = Some(limit); self } + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn background(self) -> PublicationCacheBuilder<'a, 'b, 'c, true> { PublicationCacheBuilder { session: self.session, @@ -107,16 +115,19 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { } } +#[allow(deprecated)] impl Resolvable for PublicationCacheBuilder<'_, '_, '_> { type To = ZResult; } +#[allow(deprecated)] impl Wait for PublicationCacheBuilder<'_, '_, '_> { fn wait(self) -> ::To { PublicationCache::new(self) } } +#[allow(deprecated)] impl IntoFuture for PublicationCacheBuilder<'_, '_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; @@ -126,16 +137,19 @@ impl IntoFuture for PublicationCacheBuilder<'_, '_, '_> { } } +#[allow(deprecated)] impl Resolvable for PublicationCacheBuilder<'_, '_, '_, true> { type To = ZResult<()>; } +#[allow(deprecated)] impl Wait for PublicationCacheBuilder<'_, '_, '_, true> { fn wait(self) -> ::To { PublicationCache::new(self).map(|_| ()) } } +#[allow(deprecated)] impl IntoFuture for PublicationCacheBuilder<'_, '_, '_, true> { type Output = ::To; type IntoFuture = Ready<::To>; @@ -146,12 +160,14 @@ impl IntoFuture for PublicationCacheBuilder<'_, '_, '_, true> { } #[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct PublicationCache { local_sub: Subscriber>, _queryable: Queryable>, task: TerminatableTask, } +#[allow(deprecated)] impl PublicationCache { fn new( conf: PublicationCacheBuilder<'_, '_, '_, BACKGROUND>, @@ -298,6 +314,7 @@ impl PublicationCache { /// Undeclare this [`PublicationCache`]`. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn undeclare(self) -> impl Resolve> { ResolveFuture::new(async move { let PublicationCache { @@ -313,11 +330,13 @@ impl PublicationCache { } #[zenoh_macros::internal] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn set_background(&mut self, background: bool) { self.local_sub.set_background(background); self._queryable.set_background(background); } + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn key_expr(&self) -> &KeyExpr<'static> { self.local_sub.key_expr() } diff --git a/zenoh-ext/src/querying_subscriber.rs b/zenoh-ext/src/querying_subscriber.rs index fa9981e2b5..a22be16098 100644 --- a/zenoh-ext/src/querying_subscriber.rs +++ b/zenoh-ext/src/querying_subscriber.rs @@ -33,6 +33,7 @@ use zenoh::{ /// The space of keys to use in a [`FetchingSubscriber`]. #[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub enum KeySpace { User, Liveliness, @@ -42,8 +43,10 @@ pub enum KeySpace { #[zenoh_macros::unstable] #[non_exhaustive] #[derive(Debug, Clone, Copy)] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct UserSpace; +#[allow(deprecated)] impl From for KeySpace { fn from(_: UserSpace) -> Self { KeySpace::User @@ -54,8 +57,10 @@ impl From for KeySpace { #[zenoh_macros::unstable] #[non_exhaustive] #[derive(Debug, Clone, Copy)] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct LivelinessSpace; +#[allow(deprecated)] impl From for KeySpace { fn from(_: LivelinessSpace) -> Self { KeySpace::Liveliness @@ -65,6 +70,7 @@ impl From for KeySpace { /// The builder of [`FetchingSubscriber`], allowing to configure it. #[zenoh_macros::unstable] #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct QueryingSubscriberBuilder<'a, 'b, KeySpace, Handler, const BACKGROUND: bool = false> { pub(crate) session: &'a Session, pub(crate) key_expr: ZResult>, @@ -78,9 +84,11 @@ pub struct QueryingSubscriberBuilder<'a, 'b, KeySpace, Handler, const BACKGROUND pub(crate) handler: Handler, } +#[allow(deprecated)] impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandler> { /// Add callback to [`FetchingSubscriber`]. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback( self, callback: F, @@ -100,6 +108,7 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandle /// Subscriber will not be undeclared when dropped, with the callback running /// in background until the session is closed. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback_mut( self, callback: F, @@ -112,6 +121,7 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandle /// Use the given handler to receive Samples. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn with( self, handler: Handler, @@ -146,10 +156,13 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandle } } +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, Callback> { /// Register the subscriber callback to be run in background until the session is closed. /// /// Background builder doesn't return a `FetchingSubscriber` object anymore. + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn background(self) -> QueryingSubscriberBuilder<'a, 'b, KeySpace, Callback, true> { QueryingSubscriberBuilder { session: self.session, @@ -166,6 +179,8 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, Callback QueryingSubscriberBuilder<'_, 'b, UserSpace, Handler, BACKGROUND> { @@ -175,6 +190,7 @@ impl<'b, Handler, const BACKGROUND: bool> /// to the ones that have the given [`Locality`](Locality). #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn allowed_origin(mut self, origin: Locality) -> Self { self.origin = origin; self @@ -182,6 +198,7 @@ impl<'b, Handler, const BACKGROUND: bool> /// Change the selector to be used for queries. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_selector(mut self, query_selector: IntoSelector) -> Self where IntoSelector: TryInto>, @@ -193,6 +210,7 @@ impl<'b, Handler, const BACKGROUND: bool> /// Change the target to be used for queries. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_target(mut self, query_target: QueryTarget) -> Self { self.query_target = query_target; self @@ -200,6 +218,7 @@ impl<'b, Handler, const BACKGROUND: bool> /// Change the consolidation mode to be used for queries. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_consolidation>( mut self, query_consolidation: QC, @@ -210,23 +229,28 @@ impl<'b, Handler, const BACKGROUND: bool> /// Change the accepted replies for queries. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_accept_replies(mut self, accept_replies: ReplyKeyExpr) -> Self { self.query_accept_replies = accept_replies; self } } +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl<'a, 'b, KeySpace, Handler, const BACKGROUND: bool> QueryingSubscriberBuilder<'a, 'b, KeySpace, Handler, BACKGROUND> { /// Change the timeout to be used for queries. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_timeout(mut self, query_timeout: Duration) -> Self { self.query_timeout = query_timeout; self } #[allow(clippy::type_complexity)] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn into_fetching_subscriber_builder( self, ) -> ZResult< @@ -285,6 +309,7 @@ impl<'a, 'b, KeySpace, Handler, const BACKGROUND: bool> } } +#[allow(deprecated)] impl Resolvable for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where Handler: IntoHandler, @@ -293,6 +318,7 @@ where type To = ZResult>; } +#[allow(deprecated)] impl Wait for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where KeySpace: Into + Clone, @@ -304,6 +330,7 @@ where } } +#[allow(deprecated)] impl IntoFuture for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where KeySpace: Into + Clone, @@ -318,10 +345,12 @@ where } } +#[allow(deprecated)] impl Resolvable for QueryingSubscriberBuilder<'_, '_, KeySpace, Callback, true> { type To = ZResult<()>; } +#[allow(deprecated)] impl Wait for QueryingSubscriberBuilder<'_, '_, KeySpace, Callback, true> where KeySpace: Into + Clone, @@ -331,6 +360,7 @@ where } } +#[allow(deprecated)] impl IntoFuture for QueryingSubscriberBuilder<'_, '_, KeySpace, Callback, true> where KeySpace: Into + Clone, @@ -406,6 +436,8 @@ struct InnerState { /// The builder of [`FetchingSubscriber`], allowing to configure it. #[zenoh_macros::unstable] #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] pub struct FetchingSubscriberBuilder< 'a, 'b, @@ -426,6 +458,7 @@ pub struct FetchingSubscriberBuilder< pub(crate) phantom: std::marker::PhantomData, } +#[allow(deprecated)] impl< 'a, KeySpace, @@ -452,6 +485,8 @@ where } } +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl< 'a, 'b, @@ -464,6 +499,7 @@ where { /// Add callback to [`FetchingSubscriber`]. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback( self, callback: F, @@ -483,6 +519,7 @@ where /// Subscriber will not be undeclared when dropped, with the callback running /// in background until the session is closed. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback_mut( self, callback: F, @@ -495,6 +532,7 @@ where /// Use the given handler to receive Samples. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn with( self, handler: Handler, @@ -523,6 +561,8 @@ where } } +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl< 'a, 'b, @@ -536,6 +576,7 @@ where /// Register the subscriber callback to be run in background until the session is closed. /// /// Background builder doesn't return a `FetchingSubscriber` object anymore. + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn background( self, ) -> FetchingSubscriberBuilder<'a, 'b, KeySpace, Callback, Fetch, TryIntoSample, true> @@ -552,6 +593,8 @@ where } } +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl< Handler, Fetch: FnOnce(Box) -> ZResult<()>, @@ -565,12 +608,14 @@ where /// to the ones that have the given [`Locality`](Locality). #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn allowed_origin(mut self, origin: Locality) -> Self { self.origin = origin; self } } +#[allow(deprecated)] impl< KeySpace, Handler, @@ -585,6 +630,7 @@ where type To = ZResult>; } +#[allow(deprecated)] impl< KeySpace, Handler, @@ -602,6 +648,7 @@ where } } +#[allow(deprecated)] impl< KeySpace, Handler, @@ -622,6 +669,7 @@ where } } +#[allow(deprecated)] impl< KeySpace, Fetch: FnOnce(Box) -> ZResult<()>, @@ -634,6 +682,7 @@ where type To = ZResult<()>; } +#[allow(deprecated)] impl< KeySpace, Fetch: FnOnce(Box) -> ZResult<()> + Send + Sync, @@ -652,6 +701,7 @@ where } } +#[allow(deprecated)] impl< KeySpace, Fetch: FnOnce(Box) -> ZResult<()> + Send + Sync, @@ -702,6 +752,7 @@ where /// # } /// ``` #[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct FetchingSubscriber { subscriber: Subscriber<()>, callback: Callback, @@ -709,6 +760,7 @@ pub struct FetchingSubscriber { handler: Handler, } +#[allow(deprecated)] impl std::ops::Deref for FetchingSubscriber { type Target = Handler; fn deref(&self) -> &Self::Target { @@ -716,12 +768,15 @@ impl std::ops::Deref for FetchingSubscriber { } } +#[allow(deprecated)] impl std::ops::DerefMut for FetchingSubscriber { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.handler } } +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl FetchingSubscriber { fn new< 'a, @@ -806,17 +861,20 @@ impl FetchingSubscriber { /// Undeclare this [`FetchingSubscriber`]`. #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn undeclare(self) -> impl Resolve> { self.subscriber.undeclare() } #[zenoh_macros::internal] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn set_background(&mut self, background: bool) { self.subscriber.set_background(background) } /// Return the key expression of this FetchingSubscriber #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn key_expr(&self) -> &KeyExpr<'static> { self.subscriber.key_expr() } @@ -858,6 +916,7 @@ impl FetchingSubscriber { /// # } /// ``` #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn fetch< Fetch: FnOnce(Box) -> ZResult<()> + Send + Sync, TryIntoSample, @@ -936,6 +995,8 @@ impl Drop for RepliesHandler { /// ``` #[zenoh_macros::unstable] #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] pub struct FetchBuilder< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -948,6 +1009,7 @@ pub struct FetchBuilder< callback: Callback, } +#[allow(deprecated)] impl) -> ZResult<()>, TryIntoSample> Resolvable for FetchBuilder where @@ -956,6 +1018,7 @@ where type To = ZResult<()>; } +#[allow(deprecated)] impl) -> ZResult<()>, TryIntoSample> Wait for FetchBuilder where @@ -967,6 +1030,7 @@ where } } +#[allow(deprecated)] impl) -> ZResult<()>, TryIntoSample> IntoFuture for FetchBuilder where @@ -986,6 +1050,7 @@ fn register_handler(state: Arc>, callback: Callback) - RepliesHandler { state, callback } } +#[allow(deprecated)] fn run_fetch< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -1008,10 +1073,14 @@ where } #[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] pub trait ExtractSample { + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn extract(self) -> ZResult; } +#[allow(deprecated)] impl ExtractSample for Reply { fn extract(self) -> ZResult { self.into_result().map_err(|e| zerror!("{:?}", e).into()) diff --git a/zenoh-ext/src/session_ext.rs b/zenoh-ext/src/session_ext.rs index 9d3c430aaf..1126707cc3 100644 --- a/zenoh-ext/src/session_ext.rs +++ b/zenoh-ext/src/session_ext.rs @@ -14,10 +14,12 @@ use zenoh::{key_expr::KeyExpr, session::Session, Error}; +#[allow(deprecated)] use super::PublicationCacheBuilder; /// Some extensions to the [`zenoh::Session`](zenoh::Session) #[zenoh_macros::unstable] +#[allow(deprecated)] pub trait SessionExt { // REVIEW(fuzzypixelz): this doc test is the only one to use the programmatic configuration API.. /// Examples: @@ -37,6 +39,7 @@ pub trait SessionExt { /// }).await; /// # } /// ``` + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( &'a self, pub_key_expr: TryIntoKeyExpr, @@ -46,6 +49,7 @@ pub trait SessionExt { >>::Error: Into; } +#[allow(deprecated)] impl SessionExt for Session { fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( &'a self, diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index deecce4830..afe67c9474 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -23,6 +23,7 @@ use zenoh::{ Result as ZResult, }; +#[allow(deprecated)] use crate::{ advanced_subscriber::HistoryConfig, querying_subscriber::QueryingSubscriberBuilder, AdvancedSubscriberBuilder, ExtractSample, FetchingSubscriberBuilder, RecoveryConfig, @@ -47,6 +48,8 @@ where /// Some extensions to the [`zenoh::subscriber::SubscriberBuilder`](zenoh::pubsub::SubscriberBuilder) #[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] pub trait SubscriberBuilderExt<'a, 'b, Handler> { type KeySpace; @@ -83,6 +86,7 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { /// } /// # } /// ``` + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn fetching< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -121,6 +125,7 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { /// } /// # } /// ``` + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn querying(self) -> QueryingSubscriberBuilder<'a, 'b, Self::KeySpace, Handler>; } @@ -144,6 +149,7 @@ pub trait AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> { fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; } +#[allow(deprecated)] impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilder<'a, 'b, Handler> { type KeySpace = crate::UserSpace; @@ -280,6 +286,7 @@ impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> } } +#[allow(deprecated)] impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for LivelinessSubscriberBuilder<'a, 'b, Handler> { diff --git a/zenoh-ext/tests/liveliness.rs b/zenoh-ext/tests/liveliness.rs index aebbc52b5d..cc47cdca74 100644 --- a/zenoh-ext/tests/liveliness.rs +++ b/zenoh-ext/tests/liveliness.rs @@ -19,6 +19,7 @@ use zenoh::{ }; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[allow(deprecated)] async fn test_liveliness_querying_subscriber_clique() { use std::time::Duration; @@ -97,6 +98,7 @@ async fn test_liveliness_querying_subscriber_clique() { } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[allow(deprecated)] async fn test_liveliness_querying_subscriber_brokered() { use std::time::Duration; @@ -203,6 +205,7 @@ async fn test_liveliness_querying_subscriber_brokered() { } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[allow(deprecated)] async fn test_liveliness_fetching_subscriber_clique() { use std::time::Duration; @@ -285,6 +288,7 @@ async fn test_liveliness_fetching_subscriber_clique() { } #[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[allow(deprecated)] async fn test_liveliness_fetching_subscriber_brokered() { use std::time::Duration; From 602392087868e88b36f821850fb5d214aebaf569 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 15:21:57 +0100 Subject: [PATCH 54/88] Remove z_pub_cache and z_query_sub entries from zenoh-ext examples README --- zenoh-ext/examples/examples/README.md | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/zenoh-ext/examples/examples/README.md b/zenoh-ext/examples/examples/README.md index f294d21ddb..b72e50f816 100644 --- a/zenoh-ext/examples/examples/README.md +++ b/zenoh-ext/examples/examples/README.md @@ -42,31 +42,6 @@ z_advanced_sub ``` -### z_pub_cache - - Declares a publisher and an associated publication cache with a given key expression. - All the publications are locally cached (with a configurable history size - i.e. max number of cached data per resource). The cache can be queried by a QueryingSubscriber at startup (see next example). - - Typical usage: - ```bash - z_pub_cache - ``` - or - ```bash - z_pub_cache --history 10 - ``` - -### z_query_sub - - Declares a querying subscriber with a selector. - At startup, the subscriber issuez a query (by default on the same selector than the subscription) and merge/sort/de-duplicate the query results with the publications received in parallel. - - Typical usage: - ```bash - z_query_sub - ``` - - ### z_member Group Management example: join a group and display the received group events (Join, Leave, LeaseExpired), as well as an updated group view. From dbd7d221aaed43cd19585d99e176d93d6b196159 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 15:22:27 +0100 Subject: [PATCH 55/88] Add z_advanced_pub and z_advanced_sub to zenoh-ext examples Cargo.toml --- zenoh-ext/examples/Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zenoh-ext/examples/Cargo.toml b/zenoh-ext/examples/Cargo.toml index 10d09d4af1..688c044d67 100644 --- a/zenoh-ext/examples/Cargo.toml +++ b/zenoh-ext/examples/Cargo.toml @@ -41,6 +41,14 @@ zenoh-ext = { workspace = true, features = ["unstable"] } [dev-dependencies] zenoh-config = { workspace = true } +[[example]] +name = "z_advanced_pub" +path = "examples/z_advanced_pub.rs" + +[[example]] +name = "z_advanced_sub" +path = "examples/z_advanced_sub.rs" + [[example]] name = "z_member" path = "examples/z_member.rs" From 7537985ce459ac920f6d3f36438af1655866491a Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 21:14:04 +0100 Subject: [PATCH 56/88] Add CacheConfig replies_qos option --- zenoh-ext/src/advanced_cache.rs | 78 ++++++++++++++++++++++++++++++--- zenoh-ext/src/lib.rs | 2 +- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 4c7943f38e..d4f2198ba5 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -22,15 +22,16 @@ use futures::{select, FutureExt}; use tokio::task; use zenoh::{ handlers::FifoChannelHandler, - internal::bail, + internal::{bail, traits::QoSBuilderTrait}, key_expr::{ format::{ke, kedefine}, keyexpr, KeyExpr, OwnedKeyExpr, }, liveliness::LivelinessToken, pubsub::Subscriber, + qos::{CongestionControl, Priority}, query::{Query, Queryable, ZenohParameters}, - sample::{Locality, Sample}, + sample::{Locality, Sample, SampleBuilder}, Resolvable, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_STARSTAR, }; @@ -40,11 +41,50 @@ kedefine!( pub(crate) ke_liveliness: "@adv/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", ); +#[derive(Clone, Debug)] +pub struct QoS { + priority: Priority, + congestion_control: CongestionControl, + is_express: bool, +} + +impl Default for QoS { + fn default() -> Self { + Self { + priority: Priority::Data, + congestion_control: CongestionControl::Block, + is_express: false, + } + } +} + +#[zenoh_macros::internal_trait] +impl QoSBuilderTrait for QoS { + #[allow(unused_mut)] + fn congestion_control(mut self, congestion_control: CongestionControl) -> Self { + self.congestion_control = congestion_control; + self + } + + #[allow(unused_mut)] + fn priority(mut self, priority: Priority) -> Self { + self.priority = priority; + self + } + + #[allow(unused_mut)] + fn express(mut self, is_express: bool) -> Self { + self.is_express = is_express; + self + } +} + #[derive(Debug, Clone)] /// Configure an [`AdvancedPublisher`](crate::AdvancedPublisher) cache. pub struct CacheConfig { sample_depth: usize, resources_limit: Option, + replies_qos: QoS, } impl Default for CacheConfig { @@ -52,6 +92,7 @@ impl Default for CacheConfig { Self { sample_depth: 1, resources_limit: None, + replies_qos: QoS::default(), } } } @@ -70,6 +111,12 @@ impl CacheConfig { self.resources_limit = Some(limit); self } + + /// The QoS to apply to replies. + pub fn replies_qos(mut self, qos: QoS) -> Self { + self.replies_qos = qos; + self + } } /// The builder of AdvancedCache, allowing to configure it. @@ -219,6 +266,7 @@ impl AdvancedCache { start: Option, end: Option, max: Option, + qos: &QoS, ) { if let Some(max) = max { let mut samples = VecDeque::new(); @@ -236,7 +284,16 @@ impl AdvancedCache { } } for sample in samples.drain(..).rev() { - if let Err(e) = query.reply_sample(sample.clone()).await { + if let Err(e) = query + .reply_sample( + SampleBuilder::from(sample.clone()) + .congestion_control(qos.congestion_control) + .priority(qos.priority) + .express(qos.is_express) + .into(), + ) + .await + { tracing::warn!("Error replying to query: {}", e); } } @@ -250,7 +307,16 @@ impl AdvancedCache { continue; } } - if let Err(e) = query.reply_sample(sample.clone()).await { + if let Err(e) = query + .reply_sample( + SampleBuilder::from(sample.clone()) + .congestion_control(qos.congestion_control) + .priority(qos.priority) + .express(qos.is_express) + .into(), + ) + .await + { tracing::warn!("Error replying to query: {}", e); } } @@ -296,12 +362,12 @@ impl AdvancedCache { let max = query.parameters().get("_max").and_then(|s| s.parse::().ok()); if !query.selector().key_expr().as_str().contains('*') { if let Some(queue) = cache.get(query.selector().key_expr().as_keyexpr()) { - process_queue(queue, &query, start, end, max).await; + process_queue(queue, &query, start, end, max, &history.replies_qos).await; } } else { for (key_expr, queue) in cache.iter() { if query.selector().key_expr().intersects(key_expr.borrow()) { - process_queue(queue, &query, start, end, max).await; + process_queue(queue, &query, start, end, max, &history.replies_qos).await; } } } diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 367aff98d3..5651d037a4 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -40,7 +40,7 @@ pub use crate::serialization::{ #[cfg(feature = "unstable")] #[allow(deprecated)] pub use crate::{ - advanced_cache::CacheConfig, + advanced_cache::{CacheConfig, QoS}, advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder, Sequencing}, advanced_subscriber::{ AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, RecoveryConfig, From f850eaacc93ecc7513d070cfc147d5eedeb30d8a Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 5 Dec 2024 22:42:36 +0100 Subject: [PATCH 57/88] Call cache directly from publisher --- zenoh-ext/src/advanced_cache.rs | 280 ++++++++++------------------ zenoh-ext/src/advanced_publisher.rs | 135 ++++++++++++-- zenoh/src/api/builders/sample.rs | 51 ++++- zenoh/src/api/publisher.rs | 5 + zenoh/src/api/session.rs | 5 + 5 files changed, 283 insertions(+), 193 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index d4f2198ba5..bdda3b7392 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -12,25 +12,20 @@ // ZettaScale Zenoh Team, // use std::{ - borrow::Borrow, - collections::{HashMap, VecDeque}, + collections::VecDeque, future::{IntoFuture, Ready}, + sync::{Arc, RwLock}, }; -use flume::{bounded, Sender}; -use futures::{select, FutureExt}; -use tokio::task; use zenoh::{ - handlers::FifoChannelHandler, internal::{bail, traits::QoSBuilderTrait}, key_expr::{ format::{ke, kedefine}, - keyexpr, KeyExpr, OwnedKeyExpr, + keyexpr, KeyExpr, }, liveliness::LivelinessToken, - pubsub::Subscriber, qos::{CongestionControl, Priority}, - query::{Query, Queryable, ZenohParameters}, + query::{Queryable, ZenohParameters}, sample::{Locality, Sample, SampleBuilder}, Resolvable, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_STARSTAR, }; @@ -82,16 +77,14 @@ impl QoSBuilderTrait for QoS { #[derive(Debug, Clone)] /// Configure an [`AdvancedPublisher`](crate::AdvancedPublisher) cache. pub struct CacheConfig { - sample_depth: usize, - resources_limit: Option, + max_samples: usize, replies_qos: QoS, } impl Default for CacheConfig { fn default() -> Self { Self { - sample_depth: 1, - resources_limit: None, + max_samples: 1, replies_qos: QoS::default(), } } @@ -100,15 +93,7 @@ impl Default for CacheConfig { impl CacheConfig { /// Specify how many samples to keep for each resource. pub fn max_samples(mut self, depth: usize) -> Self { - self.sample_depth = depth; - self - } - - // TODO pub fn max_age(mut self, depth: Duration) -> Self - - /// Specify the maximum total number of samples to keep. - pub fn max_total_samples(mut self, limit: usize) -> Self { - self.resources_limit = Some(limit); + self.max_samples = depth; self } @@ -124,7 +109,6 @@ pub struct AdvancedCacheBuilder<'a, 'b, 'c> { session: &'a Session, pub_key_expr: ZResult>, queryable_prefix: Option>>, - subscriber_origin: Locality, queryable_origin: Locality, history: CacheConfig, liveliness: bool, @@ -139,7 +123,6 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { session, pub_key_expr, queryable_prefix: Some(Ok((KE_ADV_PREFIX / KE_STARSTAR / KE_AT).into())), - subscriber_origin: Locality::default(), queryable_origin: Locality::default(), history: CacheConfig::default(), liveliness: false, @@ -156,14 +139,6 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { self } - /// Restrict the matching publications that will be cached by this [`AdvancedCache`] - /// to the ones that have the given [`Locality`](zenoh::sample::Locality). - #[inline] - pub fn subscriber_allowed_origin(mut self, origin: Locality) -> Self { - self.subscriber_origin = origin; - self - } - /// Change the history size for each resource. pub fn history(mut self, history: CacheConfig) -> Self { self.history = history; @@ -213,174 +188,114 @@ fn sample_in_range(sample: &Sample, start: Option, end: Option) -> boo } pub struct AdvancedCache { - _sub: Subscriber>, - _queryable: Queryable>, + cache: Arc>>, + max_samples: usize, + _queryable: Queryable<()>, _token: Option, - _stoptx: Sender, } impl AdvancedCache { fn new(conf: AdvancedCacheBuilder<'_, '_, '_>) -> ZResult { - let key_expr = conf.pub_key_expr?; + let key_expr = conf.pub_key_expr?.into_owned(); // the queryable_prefix (optional), and the key_expr for AdvancedCache's queryable ("[]/") - let (queryable_prefix, queryable_key_expr): (Option, KeyExpr) = - match conf.queryable_prefix { - None => (None, key_expr.clone()), - Some(Ok(ke)) => { - let queryable_key_expr = (&ke) / &key_expr; - (Some(ke.into()), queryable_key_expr) - } - Some(Err(e)) => bail!("Invalid key expression for queryable_prefix: {}", e), - }; + let queryable_key_expr = match conf.queryable_prefix { + None => key_expr.clone(), + Some(Ok(ke)) => (&ke) / &key_expr, + Some(Err(e)) => bail!("Invalid key expression for queryable_prefix: {}", e), + }; tracing::debug!( - "Create AdvancedCache on {} with history={:?}", + "Create AdvancedCache on {} with max_samples={:?}", &key_expr, conf.history, ); - - // declare the local subscriber that will store the local publications - let sub = conf - .session - .declare_subscriber(&key_expr) - .allowed_origin(conf.subscriber_origin) - .wait()?; + let cache = Arc::new(RwLock::new(VecDeque::new())); // declare the queryable that will answer to queries on cache let queryable = conf .session .declare_queryable(&queryable_key_expr) .allowed_origin(conf.queryable_origin) - .wait()?; - - // take local ownership of stuff to be moved into task - let sub_recv = sub.handler().clone(); - let quer_recv = queryable.handler().clone(); - let pub_key_expr = key_expr.into_owned(); - let history = conf.history; - - let (stoptx, stoprx) = bounded::(1); - task::spawn(async move { - async fn process_queue( - queue: &VecDeque, - query: &Query, - start: Option, - end: Option, - max: Option, - qos: &QoS, - ) { - if let Some(max) = max { - let mut samples = VecDeque::new(); - for sample in queue { - if sample_in_range(sample, start, end) { - if let (Some(Ok(time_range)), Some(timestamp)) = - (query.parameters().time_range(), sample.timestamp()) - { - if !time_range.contains(timestamp.get_time().to_system_time()) { - continue; + .callback({ + let cache = cache.clone(); + move |query| { + let (start, end) = query + .parameters() + .get("_sn") + .map(decode_range) + .unwrap_or((None, None)); + let max = query + .parameters() + .get("_max") + .and_then(|s| s.parse::().ok()); + if let Ok(queue) = cache.read() { + if let Some(max) = max { + let mut samples = VecDeque::new(); + for sample in queue.iter() { + if sample_in_range(sample, start, end) { + if let (Some(Ok(time_range)), Some(timestamp)) = + (query.parameters().time_range(), sample.timestamp()) + { + if !time_range + .contains(timestamp.get_time().to_system_time()) + { + continue; + } + } + samples.push_front(sample); + samples.truncate(max as usize); } } - samples.push_front(sample); - samples.truncate(max as usize); - } - } - for sample in samples.drain(..).rev() { - if let Err(e) = query - .reply_sample( - SampleBuilder::from(sample.clone()) - .congestion_control(qos.congestion_control) - .priority(qos.priority) - .express(qos.is_express) - .into(), - ) - .await - { - tracing::warn!("Error replying to query: {}", e); - } - } - } else { - for sample in queue { - if sample_in_range(sample, start, end) { - if let (Some(Ok(time_range)), Some(timestamp)) = - (query.parameters().time_range(), sample.timestamp()) - { - if !time_range.contains(timestamp.get_time().to_system_time()) { - continue; + for sample in samples.drain(..).rev() { + if let Err(e) = query + .reply_sample( + SampleBuilder::from(sample.clone()) + .congestion_control( + conf.history.replies_qos.congestion_control, + ) + .priority(conf.history.replies_qos.priority) + .express(conf.history.replies_qos.is_express) + .into(), + ) + .wait() + { + tracing::warn!("Error replying to query: {}", e); } } - if let Err(e) = query - .reply_sample( - SampleBuilder::from(sample.clone()) - .congestion_control(qos.congestion_control) - .priority(qos.priority) - .express(qos.is_express) - .into(), - ) - .await - { - tracing::warn!("Error replying to query: {}", e); - } - } - } - } - } - - let mut cache: HashMap> = - HashMap::with_capacity(history.resources_limit.unwrap_or(32)); - let limit = history.resources_limit.unwrap_or(usize::MAX); - - loop { - select!( - // on publication received by the local subscriber, store it - sample = sub_recv.recv_async() => { - if let Ok(sample) = sample { - let queryable_key_expr: KeyExpr<'_> = if let Some(prefix) = &queryable_prefix { - prefix.join(&sample.key_expr()).unwrap().into() - } else { - sample.key_expr().clone() - }; - - if let Some(queue) = cache.get_mut(queryable_key_expr.as_keyexpr()) { - if queue.len() >= history.sample_depth { - queue.pop_front(); - } - queue.push_back(sample); - } else if cache.len() >= limit { - tracing::error!("AdvancedCache on {}: resource_limit exceeded - can't cache publication for a new resource", - pub_key_expr); - } else { - let mut queue: VecDeque = VecDeque::new(); - queue.push_back(sample); - cache.insert(queryable_key_expr.into(), queue); - } - } - }, - - // on query, reply with cache content - query = quer_recv.recv_async() => { - if let Ok(query) = query { - let (start, end) = query.parameters().get("_sn").map(decode_range).unwrap_or((None, None)); - let max = query.parameters().get("_max").and_then(|s| s.parse::().ok()); - if !query.selector().key_expr().as_str().contains('*') { - if let Some(queue) = cache.get(query.selector().key_expr().as_keyexpr()) { - process_queue(queue, &query, start, end, max, &history.replies_qos).await; - } - } else { - for (key_expr, queue) in cache.iter() { - if query.selector().key_expr().intersects(key_expr.borrow()) { - process_queue(queue, &query, start, end, max, &history.replies_qos).await; + } else { + for sample in queue.iter() { + if sample_in_range(sample, start, end) { + if let (Some(Ok(time_range)), Some(timestamp)) = + (query.parameters().time_range(), sample.timestamp()) + { + if !time_range + .contains(timestamp.get_time().to_system_time()) + { + continue; + } + } + if let Err(e) = query + .reply_sample( + SampleBuilder::from(sample.clone()) + .congestion_control( + conf.history.replies_qos.congestion_control, + ) + .priority(conf.history.replies_qos.priority) + .express(conf.history.replies_qos.is_express) + .into(), + ) + .wait() + { + tracing::warn!("Error replying to query: {}", e); } } } } - }, - - // When stoptx is dropped, stop the task - _ = stoprx.recv_async().fuse() => { - return + } else { + tracing::error!("Unable to take AdvancedPublisher cache read lock"); } - ); - } - }); + } + }) + .wait()?; let token = if conf.liveliness { Some( @@ -394,10 +309,21 @@ impl AdvancedCache { }; Ok(AdvancedCache { - _sub: sub, + cache, + max_samples: conf.history.max_samples, _queryable: queryable, _token: token, - _stoptx: stoptx, }) } + + pub fn cache_sample(&self, sample: Sample) { + if let Ok(mut queue) = self.cache.write() { + if queue.len() >= self.max_samples { + queue.pop_front(); + } + queue.push_back(sample); + } else { + tracing::error!("Unable to take AdvancedPublisher cache write lock"); + } + } } diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 3993f28a61..a6bea50fd7 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -17,13 +17,16 @@ use std::{ }; use zenoh::{ - bytes::{Encoding, ZBytes}, - internal::bail, + bytes::{Encoding, OptionZBytes, ZBytes}, + internal::{ + bail, + traits::{EncodingBuilderTrait, SampleBuilderTrait, TimestampBuilderTrait}, + }, key_expr::KeyExpr, liveliness::LivelinessToken, - pubsub::{Publisher, PublisherDeleteBuilder, PublisherPutBuilder}, + pubsub::{PublicationBuilder, PublicationBuilderDelete, PublicationBuilderPut, Publisher}, qos::{CongestionControl, Priority}, - sample::{Locality, SourceInfo}, + sample::SourceInfo, session::EntityGlobalId, Resolvable, Resolve, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_EMPTY, }; @@ -126,7 +129,7 @@ impl IntoFuture for AdvancedPublisherBuilder<'_, '_, '_> { pub struct AdvancedPublisher<'a> { publisher: Publisher<'a>, seqnum: Option, - _cache: Option, + cache: Option, _token: Option, } @@ -174,7 +177,6 @@ impl<'a> AdvancedPublisher<'a> { let cache = if conf.cache { Some( AdvancedCacheBuilder::new(conf.session, Ok(key_expr.clone().into_owned())) - .subscriber_allowed_origin(Locality::SessionLocal) .history(conf.history) .queryable_prefix(&prefix) .wait()?, @@ -197,7 +199,7 @@ impl<'a> AdvancedPublisher<'a> { Ok(AdvancedPublisher { publisher, seqnum, - _cache: cache, + cache, _token: token, }) } @@ -256,18 +258,24 @@ impl<'a> AdvancedPublisher<'a> { /// # } /// ``` #[inline] - pub fn put(&self, payload: IntoZBytes) -> PublisherPutBuilder<'_> + pub fn put(&self, payload: IntoZBytes) -> AdvancedPublisherPutBuilder<'_> where IntoZBytes: Into, { - let mut put = self.publisher.put(payload); + let mut builder = self.publisher.put(payload); if let Some(seqnum) = &self.seqnum { - put = put.source_info(SourceInfo::new( + builder = builder.source_info(SourceInfo::new( Some(self.publisher.id()), Some(seqnum.fetch_add(1, Ordering::Relaxed)), )); } - put + if let Some(hlc) = self.publisher.session().hlc() { + builder = builder.timestamp(hlc.new_timestamp()); + } + AdvancedPublisherPutBuilder { + builder, + cache: self.cache.as_ref(), + } } /// Delete data. @@ -282,15 +290,21 @@ impl<'a> AdvancedPublisher<'a> { /// publisher.delete().await.unwrap(); /// # } /// ``` - pub fn delete(&self) -> PublisherDeleteBuilder<'_> { - let mut delete = self.publisher.delete(); + pub fn delete(&self) -> AdvancedPublisherDeleteBuilder<'_> { + let mut builder = self.publisher.delete(); if let Some(seqnum) = &self.seqnum { - delete = delete.source_info(SourceInfo::new( + builder = builder.source_info(SourceInfo::new( Some(self.publisher.id()), Some(seqnum.fetch_add(1, Ordering::Relaxed)), )); } - delete + if let Some(hlc) = self.publisher.session().hlc() { + builder = builder.timestamp(hlc.new_timestamp()); + } + AdvancedPublisherDeleteBuilder { + builder, + cache: self.cache.as_ref(), + } } /// Return the [`MatchingStatus`](zenoh::matching::MatchingStatus) of the publisher. @@ -362,3 +376,94 @@ impl<'a> AdvancedPublisher<'a> { self.publisher.undeclare() } } + +pub type AdvancedPublisherPutBuilder<'a> = AdvancedPublicationBuilder<'a, PublicationBuilderPut>; +pub type AdvancedPublisherDeleteBuilder<'a> = + AdvancedPublicationBuilder<'a, PublicationBuilderDelete>; + +#[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] +#[derive(Clone)] +pub struct AdvancedPublicationBuilder<'a, P> { + pub(crate) builder: PublicationBuilder<&'a Publisher<'a>, P>, + pub(crate) cache: Option<&'a AdvancedCache>, +} + +#[zenoh_macros::internal_trait] +impl EncodingBuilderTrait for AdvancedPublicationBuilder<'_, PublicationBuilderPut> { + fn encoding>(self, encoding: T) -> Self { + Self { + builder: self.builder.encoding(encoding), + ..self + } + } +} + +#[zenoh_macros::internal_trait] +impl

SampleBuilderTrait for AdvancedPublicationBuilder<'_, P> { + #[cfg(feature = "unstable")] + fn source_info(self, source_info: SourceInfo) -> Self { + Self { + builder: self.builder.source_info(source_info), + ..self + } + } + fn attachment>(self, attachment: TA) -> Self { + let attachment: OptionZBytes = attachment.into(); + Self { + builder: self.builder.attachment(attachment), + ..self + } + } +} + +#[zenoh_macros::internal_trait] +impl

TimestampBuilderTrait for AdvancedPublicationBuilder<'_, P> { + fn timestamp>>(self, timestamp: TS) -> Self { + Self { + builder: self.builder.timestamp(timestamp), + ..self + } + } +} + +impl

Resolvable for AdvancedPublicationBuilder<'_, P> { + type To = ZResult<()>; +} + +impl Wait for AdvancedPublisherPutBuilder<'_> { + #[inline] + fn wait(self) -> ::To { + if let Some(cache) = self.cache { + cache.cache_sample(zenoh::sample::Sample::from(&self.builder)); + } + self.builder.wait() + } +} + +impl Wait for AdvancedPublisherDeleteBuilder<'_> { + #[inline] + fn wait(self) -> ::To { + if let Some(cache) = self.cache { + cache.cache_sample(zenoh::sample::Sample::from(&self.builder)); + } + self.builder.wait() + } +} + +impl IntoFuture for AdvancedPublisherPutBuilder<'_> { + type Output = ::To; + type IntoFuture = Ready<::To>; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} + +impl IntoFuture for AdvancedPublisherDeleteBuilder<'_> { + type Output = ::To; + type IntoFuture = Ready<::To>; + + fn into_future(self) -> Self::IntoFuture { + std::future::ready(self.wait()) + } +} diff --git a/zenoh/src/api/builders/sample.rs b/zenoh/src/api/builders/sample.rs index ebec624626..f00875d185 100644 --- a/zenoh/src/api/builders/sample.rs +++ b/zenoh/src/api/builders/sample.rs @@ -26,9 +26,12 @@ use crate::api::{ publisher::Priority, sample::{QoS, QoSBuilder, Sample, SampleKind}, }; +#[zenoh_macros::internal] +use crate::pubsub::{ + PublicationBuilder, PublicationBuilderDelete, PublicationBuilderPut, Publisher, +}; #[cfg(feature = "unstable")] use crate::sample::SourceInfo; - pub trait QoSBuilderTrait { /// Change the `congestion_control` to apply when routing the data. fn congestion_control(self, congestion_control: CongestionControl) -> Self; @@ -285,3 +288,49 @@ impl From> for Sample { sample_builder.sample } } + +#[zenoh_macros::internal] +impl From<&PublicationBuilder<&Publisher<'_>, PublicationBuilderPut>> for Sample { + fn from(builder: &PublicationBuilder<&Publisher<'_>, PublicationBuilderPut>) -> Self { + Sample { + key_expr: builder.publisher.key_expr.clone().into_owned(), + payload: builder.kind.payload.clone(), + kind: SampleKind::Put, + encoding: builder.kind.encoding.clone(), + timestamp: builder.timestamp, + qos: QoSBuilder::from(QoS::default()) + .congestion_control(builder.publisher.congestion_control) + .priority(builder.publisher.priority) + .express(builder.publisher.is_express) + .into(), + #[cfg(feature = "unstable")] + reliability: builder.publisher.reliability, + #[cfg(feature = "unstable")] + source_info: builder.source_info.clone(), + attachment: builder.attachment.clone(), + } + } +} + +#[zenoh_macros::internal] +impl From<&PublicationBuilder<&Publisher<'_>, PublicationBuilderDelete>> for Sample { + fn from(builder: &PublicationBuilder<&Publisher<'_>, PublicationBuilderDelete>) -> Self { + Sample { + key_expr: builder.publisher.key_expr.clone().into_owned(), + payload: ZBytes::new(), + kind: SampleKind::Put, + encoding: Encoding::ZENOH_BYTES, + timestamp: builder.timestamp, + qos: QoSBuilder::from(QoS::default()) + .congestion_control(builder.publisher.congestion_control) + .priority(builder.publisher.priority) + .express(builder.publisher.is_express) + .into(), + #[cfg(feature = "unstable")] + reliability: builder.publisher.reliability, + #[cfg(feature = "unstable")] + source_info: builder.source_info.clone(), + attachment: builder.attachment.clone(), + } + } +} diff --git a/zenoh/src/api/publisher.rs b/zenoh/src/api/publisher.rs index 78d44fc794..24d075b22e 100644 --- a/zenoh/src/api/publisher.rs +++ b/zenoh/src/api/publisher.rs @@ -313,6 +313,11 @@ impl<'a> Publisher<'a> { } self.session.undeclare_publisher_inner(self.id) } + + #[zenoh_macros::internal] + pub fn session(&self) -> crate::Session { + self.session.session() + } } impl<'a> UndeclarableSealed<()> for Publisher<'a> { diff --git a/zenoh/src/api/session.rs b/zenoh/src/api/session.rs index 49d20be27e..d22d52ee25 100644 --- a/zenoh/src/api/session.rs +++ b/zenoh/src/api/session.rs @@ -608,6 +608,11 @@ impl WeakSession { *weak += 1; Self(session.clone()) } + + #[zenoh_macros::internal] + pub(crate) fn session(&self) -> Session { + Session(self.0.clone()) + } } impl Clone for WeakSession { From 1799a9582517fecef158d00fc2ffdd349025b404 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 6 Dec 2024 12:01:27 +0100 Subject: [PATCH 58/88] Update doc --- zenoh-ext/src/advanced_cache.rs | 4 +++- zenoh-ext/src/advanced_publisher.rs | 3 ++- zenoh-ext/src/advanced_subscriber.rs | 3 ++- zenoh-ext/src/lib.rs | 2 +- zenoh-ext/src/publication_cache.rs | 1 + zenoh-ext/src/querying_subscriber.rs | 1 + 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index bdda3b7392..7e5ccb37e2 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -36,6 +36,7 @@ kedefine!( pub(crate) ke_liveliness: "@adv/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", ); +/// Configure replies QoS. #[derive(Clone, Debug)] pub struct QoS { priority: Priority, @@ -104,7 +105,7 @@ impl CacheConfig { } } -/// The builder of AdvancedCache, allowing to configure it. +/// The builder of an [`AdvancedCache`], allowing to configure it. pub struct AdvancedCacheBuilder<'a, 'b, 'c> { session: &'a Session, pub_key_expr: ZResult>, @@ -187,6 +188,7 @@ fn sample_in_range(sample: &Sample, start: Option, end: Option) -> boo } } +/// [`AdvancedCache`]. pub struct AdvancedCache { cache: Arc>>, max_samples: usize, diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index a6bea50fd7..3798c986d8 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -34,7 +34,7 @@ use zenoh::{ use crate::advanced_cache::{AdvancedCache, AdvancedCacheBuilder, CacheConfig, KE_UHLC}; #[derive(PartialEq)] -pub enum Sequencing { +pub(crate) enum Sequencing { None, Timestamp, SequenceNumber, @@ -126,6 +126,7 @@ impl IntoFuture for AdvancedPublisherBuilder<'_, '_, '_> { } } +/// [`AdvancedPublisher`]. pub struct AdvancedPublisher<'a> { publisher: Publisher<'a>, seqnum: Option, diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 46c55134be..16bfa95df7 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -108,7 +108,7 @@ impl RecoveryConfig { } } -/// The builder of AdvancedSubscriber, allowing to configure it. +/// The builder of an [`AdvancedSubscriber`], allowing to configure it. #[zenoh_macros::unstable] pub struct AdvancedSubscriberBuilder<'a, 'b, 'c, Handler, const BACKGROUND: bool = false> { pub(crate) session: &'a Session, @@ -373,6 +373,7 @@ struct SourceState { pending_samples: BTreeMap, } +/// [`AdvancedSubscriber`]. #[zenoh_macros::unstable] pub struct AdvancedSubscriber { statesref: Arc>, diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 5651d037a4..850090b02c 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -41,7 +41,7 @@ pub use crate::serialization::{ #[allow(deprecated)] pub use crate::{ advanced_cache::{CacheConfig, QoS}, - advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder, Sequencing}, + advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder}, advanced_subscriber::{ AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, RecoveryConfig, }, diff --git a/zenoh-ext/src/publication_cache.rs b/zenoh-ext/src/publication_cache.rs index 37989d5c8f..de36703642 100644 --- a/zenoh-ext/src/publication_cache.rs +++ b/zenoh-ext/src/publication_cache.rs @@ -159,6 +159,7 @@ impl IntoFuture for PublicationCacheBuilder<'_, '_, '_, true> { } } +/// [`PublicationCache`]. #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct PublicationCache { diff --git a/zenoh-ext/src/querying_subscriber.rs b/zenoh-ext/src/querying_subscriber.rs index a22be16098..3935111ee5 100644 --- a/zenoh-ext/src/querying_subscriber.rs +++ b/zenoh-ext/src/querying_subscriber.rs @@ -1072,6 +1072,7 @@ where })) } +/// [`ExtractSample`]. #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] #[allow(deprecated)] From feeb7ef0fdbd78bf78c2b34d464782687676cb19 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 6 Dec 2024 12:05:29 +0100 Subject: [PATCH 59/88] Add missing unstable tags --- zenoh-ext/src/advanced_publisher.rs | 2 ++ zenoh-ext/src/advanced_subscriber.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 3798c986d8..07745d4c02 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -42,6 +42,7 @@ pub(crate) enum Sequencing { /// The builder of PublicationCache, allowing to configure it. #[must_use = "Resolvables do nothing unless you resolve them using the `res` method from either `SyncResolve` or `AsyncResolve`"] +#[zenoh_macros::unstable] pub struct AdvancedPublisherBuilder<'a, 'b, 'c> { session: &'a Session, pub_key_expr: ZResult>, @@ -127,6 +128,7 @@ impl IntoFuture for AdvancedPublisherBuilder<'_, '_, '_> { } /// [`AdvancedPublisher`]. +#[zenoh_macros::unstable] pub struct AdvancedPublisher<'a> { publisher: Publisher<'a>, seqnum: Option, diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 16bfa95df7..1b227cde09 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -48,6 +48,7 @@ use crate::advanced_cache::{ke_liveliness, KE_UHLC}; #[derive(Debug, Default, Clone)] /// Configure query for historical data. +#[zenoh_macros::unstable] pub struct HistoryConfig { liveliness: bool, sample_depth: Option, @@ -59,7 +60,6 @@ impl HistoryConfig { /// /// Let joiner detection can only be achieved for Publishers that enable publisher_detection. /// History can only be retransmitted by Publishers that enable caching. - #[zenoh_macros::unstable] #[inline] pub fn detect_late_publishers(mut self) -> Self { self.liveliness = true; @@ -81,6 +81,7 @@ impl HistoryConfig { #[derive(Default)] /// Configure retransmission. +#[zenoh_macros::unstable] pub struct RecoveryConfig { periodic_queries: Option, } From cd6843154c9b907834341b2290eae7f56e1d0462 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 6 Dec 2024 12:12:59 +0100 Subject: [PATCH 60/88] Add missing unstable tags --- zenoh-ext/src/subscriber_ext.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index afe67c9474..50903c0039 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -130,6 +130,7 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { } /// Some extensions to the [`zenoh::subscriber::SubscriberBuilder`](zenoh::pubsub::SubscriberBuilder) +#[zenoh_macros::unstable] pub trait AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> { /// Enable query for historical data. /// From c9cc9638dc0ea30186970fa40614f955ef441fec Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 6 Dec 2024 12:15:12 +0100 Subject: [PATCH 61/88] Add missing unstable tags --- zenoh-ext/src/advanced_cache.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 7e5ccb37e2..7d5ee9a968 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -36,6 +36,7 @@ kedefine!( pub(crate) ke_liveliness: "@adv/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", ); +#[zenoh_macros::unstable] /// Configure replies QoS. #[derive(Clone, Debug)] pub struct QoS { @@ -77,6 +78,7 @@ impl QoSBuilderTrait for QoS { #[derive(Debug, Clone)] /// Configure an [`AdvancedPublisher`](crate::AdvancedPublisher) cache. +#[zenoh_macros::unstable] pub struct CacheConfig { max_samples: usize, replies_qos: QoS, @@ -106,6 +108,7 @@ impl CacheConfig { } /// The builder of an [`AdvancedCache`], allowing to configure it. +#[zenoh_macros::unstable] pub struct AdvancedCacheBuilder<'a, 'b, 'c> { session: &'a Session, pub_key_expr: ZResult>, @@ -189,6 +192,7 @@ fn sample_in_range(sample: &Sample, start: Option, end: Option) -> boo } /// [`AdvancedCache`]. +#[zenoh_macros::unstable] pub struct AdvancedCache { cache: Arc>>, max_samples: usize, From 2d6550d88e38b40a9f83e3417d71f2c332dd3696 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Fri, 6 Dec 2024 12:39:38 +0100 Subject: [PATCH 62/88] Add unstable tag everywhere --- zenoh-ext/src/advanced_cache.rs | 24 ++++++++++- zenoh-ext/src/advanced_publisher.rs | 42 ++++++++++++++++++- zenoh-ext/src/advanced_subscriber.rs | 23 +++++++++++ zenoh-ext/src/publication_cache.rs | 21 ++++++++++ zenoh-ext/src/publisher_ext.rs | 7 ++++ zenoh-ext/src/querying_subscriber.rs | 60 ++++++++++++++++++++++++++++ zenoh-ext/src/session_ext.rs | 4 ++ zenoh-ext/src/subscriber_ext.rs | 18 +++++++++ 8 files changed, 197 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 7d5ee9a968..dcc46e5207 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -45,6 +45,7 @@ pub struct QoS { is_express: bool, } +#[zenoh_macros::unstable] impl Default for QoS { fn default() -> Self { Self { @@ -56,20 +57,24 @@ impl Default for QoS { } #[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] impl QoSBuilderTrait for QoS { #[allow(unused_mut)] + #[zenoh_macros::unstable] fn congestion_control(mut self, congestion_control: CongestionControl) -> Self { self.congestion_control = congestion_control; self } #[allow(unused_mut)] + #[zenoh_macros::unstable] fn priority(mut self, priority: Priority) -> Self { self.priority = priority; self } #[allow(unused_mut)] + #[zenoh_macros::unstable] fn express(mut self, is_express: bool) -> Self { self.is_express = is_express; self @@ -84,6 +89,7 @@ pub struct CacheConfig { replies_qos: QoS, } +#[zenoh_macros::unstable] impl Default for CacheConfig { fn default() -> Self { Self { @@ -93,14 +99,17 @@ impl Default for CacheConfig { } } +#[zenoh_macros::unstable] impl CacheConfig { /// Specify how many samples to keep for each resource. + #[zenoh_macros::unstable] pub fn max_samples(mut self, depth: usize) -> Self { self.max_samples = depth; self } /// The QoS to apply to replies. + #[zenoh_macros::unstable] pub fn replies_qos(mut self, qos: QoS) -> Self { self.replies_qos = qos; self @@ -118,7 +127,9 @@ pub struct AdvancedCacheBuilder<'a, 'b, 'c> { liveliness: bool, } +#[zenoh_macros::unstable] impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { + #[zenoh_macros::unstable] pub(crate) fn new( session: &'a Session, pub_key_expr: ZResult>, @@ -134,6 +145,7 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { } /// Change the prefix used for queryable. + #[zenoh_macros::unstable] pub fn queryable_prefix(mut self, queryable_prefix: TryIntoKeyExpr) -> Self where TryIntoKeyExpr: TryInto>, @@ -144,31 +156,37 @@ impl<'a, 'b, 'c> AdvancedCacheBuilder<'a, 'b, 'c> { } /// Change the history size for each resource. + #[zenoh_macros::unstable] pub fn history(mut self, history: CacheConfig) -> Self { self.history = history; self } } +#[zenoh_macros::unstable] impl Resolvable for AdvancedCacheBuilder<'_, '_, '_> { type To = ZResult; } +#[zenoh_macros::unstable] impl Wait for AdvancedCacheBuilder<'_, '_, '_> { fn wait(self) -> ::To { AdvancedCache::new(self) } } +#[zenoh_macros::unstable] impl IntoFuture for AdvancedCacheBuilder<'_, '_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } } +#[zenoh_macros::unstable] fn decode_range(range: &str) -> (Option, Option) { let mut split = range.split(".."); let start = split.next().and_then(|s| s.parse::().ok()); @@ -176,6 +194,7 @@ fn decode_range(range: &str) -> (Option, Option) { (start, end) } +#[zenoh_macros::unstable] fn sample_in_range(sample: &Sample, start: Option, end: Option) -> bool { if start.is_none() && end.is_none() { true @@ -200,7 +219,9 @@ pub struct AdvancedCache { _token: Option, } +#[zenoh_macros::unstable] impl AdvancedCache { + #[zenoh_macros::unstable] fn new(conf: AdvancedCacheBuilder<'_, '_, '_>) -> ZResult { let key_expr = conf.pub_key_expr?.into_owned(); // the queryable_prefix (optional), and the key_expr for AdvancedCache's queryable ("[]/") @@ -322,7 +343,8 @@ impl AdvancedCache { }) } - pub fn cache_sample(&self, sample: Sample) { + #[zenoh_macros::unstable] + pub(crate) fn cache_sample(&self, sample: Sample) { if let Ok(mut queue) = self.cache.write() { if queue.len() >= self.max_samples { queue.pop_front(); diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 07745d4c02..2f2ade7bd3 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -34,6 +34,7 @@ use zenoh::{ use crate::advanced_cache::{AdvancedCache, AdvancedCacheBuilder, CacheConfig, KE_UHLC}; #[derive(PartialEq)] +#[zenoh_macros::unstable] pub(crate) enum Sequencing { None, Timestamp, @@ -53,7 +54,9 @@ pub struct AdvancedPublisherBuilder<'a, 'b, 'c> { history: CacheConfig, } +#[zenoh_macros::unstable] impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { + #[zenoh_macros::unstable] pub(crate) fn new( session: &'a Session, pub_key_expr: ZResult>, @@ -72,12 +75,14 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. + #[zenoh_macros::unstable] pub fn sample_miss_detection(mut self) -> Self { self.sequencing = Sequencing::SequenceNumber; self } /// Change the history size for each resource. + #[zenoh_macros::unstable] pub fn cache(mut self, config: CacheConfig) -> Self { self.cache = true; if self.sequencing == Sequencing::None { @@ -90,6 +95,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. + #[zenoh_macros::unstable] pub fn publisher_detection(mut self) -> Self { self.liveliness = true; self @@ -98,6 +104,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { /// A key expression added to the liveliness token key expression /// and to the cache queryable key expression. /// It can be used to convey meta data. + #[zenoh_macros::unstable] pub fn publisher_detection_metadata(mut self, meta: TryIntoKeyExpr) -> Self where TryIntoKeyExpr: TryInto>, @@ -108,20 +115,25 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { } } +#[zenoh_macros::unstable] impl<'a> Resolvable for AdvancedPublisherBuilder<'a, '_, '_> { type To = ZResult>; } +#[zenoh_macros::unstable] impl Wait for AdvancedPublisherBuilder<'_, '_, '_> { + #[zenoh_macros::unstable] fn wait(self) -> ::To { AdvancedPublisher::new(self) } } +#[zenoh_macros::unstable] impl IntoFuture for AdvancedPublisherBuilder<'_, '_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } @@ -136,7 +148,9 @@ pub struct AdvancedPublisher<'a> { _token: Option, } +#[zenoh_macros::unstable] impl<'a> AdvancedPublisher<'a> { + #[zenoh_macros::unstable] fn new(conf: AdvancedPublisherBuilder<'a, '_, '_>) -> ZResult { let key_expr = conf.pub_key_expr?; let meta = match conf.meta_key_expr { @@ -221,29 +235,34 @@ impl<'a> AdvancedPublisher<'a> { /// let publisher_id = publisher.id(); /// # } /// ``` + #[zenoh_macros::unstable] pub fn id(&self) -> EntityGlobalId { self.publisher.id() } #[inline] + #[zenoh_macros::unstable] pub fn key_expr(&self) -> &KeyExpr<'a> { self.publisher.key_expr() } /// Get the [`Encoding`] used when publishing data. #[inline] + #[zenoh_macros::unstable] pub fn encoding(&self) -> &Encoding { self.publisher.encoding() } /// Get the `congestion_control` applied when routing the data. #[inline] + #[zenoh_macros::unstable] pub fn congestion_control(&self) -> CongestionControl { self.publisher.congestion_control() } /// Get the priority of the written data. #[inline] + #[zenoh_macros::unstable] pub fn priority(&self) -> Priority { self.publisher.priority() } @@ -261,6 +280,7 @@ impl<'a> AdvancedPublisher<'a> { /// # } /// ``` #[inline] + #[zenoh_macros::unstable] pub fn put(&self, payload: IntoZBytes) -> AdvancedPublisherPutBuilder<'_> where IntoZBytes: Into, @@ -293,6 +313,7 @@ impl<'a> AdvancedPublisher<'a> { /// publisher.delete().await.unwrap(); /// # } /// ``` + #[zenoh_macros::unstable] pub fn delete(&self) -> AdvancedPublisherDeleteBuilder<'_> { let mut builder = self.publisher.delete(); if let Some(seqnum) = &self.seqnum { @@ -375,24 +396,30 @@ impl<'a> AdvancedPublisher<'a> { /// publisher.undeclare().await.unwrap(); /// # } /// ``` + #[zenoh_macros::unstable] pub fn undeclare(self) -> impl Resolve> + 'a { self.publisher.undeclare() } } +#[zenoh_macros::unstable] pub type AdvancedPublisherPutBuilder<'a> = AdvancedPublicationBuilder<'a, PublicationBuilderPut>; +#[zenoh_macros::unstable] pub type AdvancedPublisherDeleteBuilder<'a> = AdvancedPublicationBuilder<'a, PublicationBuilderDelete>; #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] #[derive(Clone)] +#[zenoh_macros::unstable] pub struct AdvancedPublicationBuilder<'a, P> { pub(crate) builder: PublicationBuilder<&'a Publisher<'a>, P>, pub(crate) cache: Option<&'a AdvancedCache>, } #[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] impl EncodingBuilderTrait for AdvancedPublicationBuilder<'_, PublicationBuilderPut> { + #[zenoh_macros::unstable] fn encoding>(self, encoding: T) -> Self { Self { builder: self.builder.encoding(encoding), @@ -402,14 +429,16 @@ impl EncodingBuilderTrait for AdvancedPublicationBuilder<'_, PublicationBuilderP } #[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] impl

SampleBuilderTrait for AdvancedPublicationBuilder<'_, P> { - #[cfg(feature = "unstable")] + #[zenoh_macros::unstable] fn source_info(self, source_info: SourceInfo) -> Self { Self { builder: self.builder.source_info(source_info), ..self } } + #[zenoh_macros::unstable] fn attachment>(self, attachment: TA) -> Self { let attachment: OptionZBytes = attachment.into(); Self { @@ -420,7 +449,9 @@ impl

SampleBuilderTrait for AdvancedPublicationBuilder<'_, P> { } #[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] impl

TimestampBuilderTrait for AdvancedPublicationBuilder<'_, P> { + #[zenoh_macros::unstable] fn timestamp>>(self, timestamp: TS) -> Self { Self { builder: self.builder.timestamp(timestamp), @@ -429,12 +460,15 @@ impl

TimestampBuilderTrait for AdvancedPublicationBuilder<'_, P> { } } +#[zenoh_macros::unstable] impl

Resolvable for AdvancedPublicationBuilder<'_, P> { type To = ZResult<()>; } +#[zenoh_macros::unstable] impl Wait for AdvancedPublisherPutBuilder<'_> { #[inline] + #[zenoh_macros::unstable] fn wait(self) -> ::To { if let Some(cache) = self.cache { cache.cache_sample(zenoh::sample::Sample::from(&self.builder)); @@ -443,8 +477,10 @@ impl Wait for AdvancedPublisherPutBuilder<'_> { } } +#[zenoh_macros::unstable] impl Wait for AdvancedPublisherDeleteBuilder<'_> { #[inline] + #[zenoh_macros::unstable] fn wait(self) -> ::To { if let Some(cache) = self.cache { cache.cache_sample(zenoh::sample::Sample::from(&self.builder)); @@ -453,19 +489,23 @@ impl Wait for AdvancedPublisherDeleteBuilder<'_> { } } +#[zenoh_macros::unstable] impl IntoFuture for AdvancedPublisherPutBuilder<'_> { type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } } +#[zenoh_macros::unstable] impl IntoFuture for AdvancedPublisherDeleteBuilder<'_> { type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 1b227cde09..6e4f821b29 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -55,24 +55,28 @@ pub struct HistoryConfig { age: Option, } +#[zenoh_macros::unstable] impl HistoryConfig { /// Enable detection of late joiner publishers and query for their historical data. /// /// Let joiner detection can only be achieved for Publishers that enable publisher_detection. /// History can only be retransmitted by Publishers that enable caching. #[inline] + #[zenoh_macros::unstable] pub fn detect_late_publishers(mut self) -> Self { self.liveliness = true; self } /// Specify how many samples to query for each resource. + #[zenoh_macros::unstable] pub fn max_samples(mut self, depth: usize) -> Self { self.sample_depth = Some(depth); self } /// Specify the maximum age of samples to query. + #[zenoh_macros::unstable] pub fn max_age(mut self, seconds: f64) -> Self { self.age = Some(seconds); self @@ -94,6 +98,7 @@ impl std::fmt::Debug for RecoveryConfig { } } +#[zenoh_macros::unstable] impl RecoveryConfig { /// Enable periodic queries for not yet received Samples and specify their period. /// @@ -126,6 +131,7 @@ pub struct AdvancedSubscriberBuilder<'a, 'b, 'c, Handler, const BACKGROUND: bool #[zenoh_macros::unstable] impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, '_, Handler> { + #[zenoh_macros::unstable] pub(crate) fn new( session: &'a Session, key_expr: ZResult>, @@ -151,6 +157,7 @@ impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, '_, Handler> { impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { /// Add callback to AdvancedSubscriber. #[inline] + #[zenoh_macros::unstable] pub fn callback( self, callback: Callback, @@ -177,6 +184,7 @@ impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { /// Using this guarantees that your callback will never be called concurrently. /// If your callback is also accepted by the [`callback`](AdvancedSubscriberBuilder::callback) method, we suggest you use it instead of `callback_mut` #[inline] + #[zenoh_macros::unstable] pub fn callback_mut( self, callback: CallbackMut, @@ -189,6 +197,7 @@ impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { /// Make the built AdvancedSubscriber an [`AdvancedSubscriber`](AdvancedSubscriber). #[inline] + #[zenoh_macros::unstable] pub fn with(self, handler: Handler) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> where Handler: IntoHandler, @@ -257,6 +266,7 @@ impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { } /// Allow this subscriber to be detected through liveliness. + #[zenoh_macros::unstable] pub fn subscriber_detection(mut self) -> Self { self.liveliness = true; self @@ -264,6 +274,7 @@ impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { /// A key expression added to the liveliness token key expression. /// It can be used to convey meta data. + #[zenoh_macros::unstable] pub fn subscriber_detection_metadata(mut self, meta: TryIntoKeyExpr) -> Self where TryIntoKeyExpr: TryInto>, @@ -273,6 +284,7 @@ impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { self } + #[zenoh_macros::unstable] fn with_static_keys(self) -> AdvancedSubscriberBuilder<'a, 'static, 'static, Handler> { AdvancedSubscriberBuilder { session: self.session, @@ -289,6 +301,7 @@ impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { } } +#[zenoh_macros::unstable] impl Resolvable for AdvancedSubscriberBuilder<'_, '_, '_, Handler> where Handler: IntoHandler, @@ -297,16 +310,19 @@ where type To = ZResult>; } +#[zenoh_macros::unstable] impl Wait for AdvancedSubscriberBuilder<'_, '_, '_, Handler> where Handler: IntoHandler + Send, Handler::Handler: Send, { + #[zenoh_macros::unstable] fn wait(self) -> ::To { AdvancedSubscriber::new(self.with_static_keys()) } } +#[zenoh_macros::unstable] impl IntoFuture for AdvancedSubscriberBuilder<'_, '_, '_, Handler> where Handler: IntoHandler + Send, @@ -315,11 +331,13 @@ where type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } } +#[zenoh_macros::unstable] struct Period { timer: Timer, period: Duration, @@ -341,13 +359,16 @@ struct State { miss_handlers: HashMap>, } +#[zenoh_macros::unstable] impl State { + #[zenoh_macros::unstable] fn register_miss_callback(&mut self, callback: Callback) -> usize { let id = self.next_id; self.next_id += 1; self.miss_handlers.insert(id, callback); id } + #[zenoh_macros::unstable] fn unregister_miss_callback(&mut self, id: &usize) { self.miss_handlers.remove(id); } @@ -892,6 +913,7 @@ impl AdvancedSubscriber { /// Close this AdvancedSubscriber #[inline] + #[zenoh_macros::unstable] pub fn close(self) -> impl Resolve> { self._subscriber.undeclare() } @@ -1176,6 +1198,7 @@ impl<'a> SampleMissListenerBuilder<'a, Callback> { /// Register the sample miss notification callback to be run in background until the adanced subscriber is undeclared. /// /// Background builder doesn't return a `SampleMissHandler` object anymore. + #[zenoh_macros::unstable] pub fn background(self) -> SampleMissListenerBuilder<'a, Callback, true> { SampleMissListenerBuilder { statesref: self.statesref, diff --git a/zenoh-ext/src/publication_cache.rs b/zenoh-ext/src/publication_cache.rs index de36703642..bdc1d3d11e 100644 --- a/zenoh-ext/src/publication_cache.rs +++ b/zenoh-ext/src/publication_cache.rs @@ -43,6 +43,7 @@ pub struct PublicationCacheBuilder<'a, 'b, 'c, const BACKGROUND: bool = false> { } #[allow(deprecated)] +#[zenoh_macros::unstable] impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { pub(crate) fn new( session: &'a Session, @@ -60,6 +61,7 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { } /// Change the prefix used for queryable. + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn queryable_prefix(mut self, queryable_prefix: TryIntoKeyExpr) -> Self where @@ -81,6 +83,7 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { } /// Set completeness option for the queryable. + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn queryable_complete(mut self, complete: bool) -> Self { self.complete = Some(complete); @@ -88,6 +91,7 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { } /// Change the history size for each resource. + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn history(mut self, history: usize) -> Self { self.history = history; @@ -95,12 +99,14 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { } /// Change the limit number of cached resources. + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn resources_limit(mut self, limit: usize) -> Self { self.resources_limit = Some(limit); self } + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn background(self) -> PublicationCacheBuilder<'a, 'b, 'c, true> { PublicationCacheBuilder { @@ -115,45 +121,55 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Resolvable for PublicationCacheBuilder<'_, '_, '_> { type To = ZResult; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Wait for PublicationCacheBuilder<'_, '_, '_> { + #[zenoh_macros::unstable] fn wait(self) -> ::To { PublicationCache::new(self) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl IntoFuture for PublicationCacheBuilder<'_, '_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Resolvable for PublicationCacheBuilder<'_, '_, '_, true> { type To = ZResult<()>; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Wait for PublicationCacheBuilder<'_, '_, '_, true> { + #[zenoh_macros::unstable] fn wait(self) -> ::To { PublicationCache::new(self).map(|_| ()) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl IntoFuture for PublicationCacheBuilder<'_, '_, '_, true> { type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } @@ -168,8 +184,10 @@ pub struct PublicationCache { task: TerminatableTask, } +#[zenoh_macros::unstable] #[allow(deprecated)] impl PublicationCache { + #[zenoh_macros::unstable] fn new( conf: PublicationCacheBuilder<'_, '_, '_, BACKGROUND>, ) -> ZResult { @@ -314,6 +332,7 @@ impl PublicationCache { } /// Undeclare this [`PublicationCache`]`. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn undeclare(self) -> impl Resolve> { @@ -331,12 +350,14 @@ impl PublicationCache { } #[zenoh_macros::internal] + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn set_background(&mut self, background: bool) { self.local_sub.set_background(background); self._queryable.set_background(background); } + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn key_expr(&self) -> &KeyExpr<'static> { self.local_sub.key_expr() diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index faa01fe124..f1ba1698d1 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -22,24 +22,29 @@ pub trait AdvancedPublisherBuilderExt<'a, 'b, 'c> { /// optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. + #[zenoh_macros::unstable] fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c>; /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. /// /// Retransmission can only be achieved if cache is enabled. + #[zenoh_macros::unstable] fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. + #[zenoh_macros::unstable] fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; } +#[zenoh_macros::unstable] impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { /// Allow matching Subscribers to detect lost samples and /// optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. + #[zenoh_macros::unstable] fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self.session, self.key_expr).cache(config) } @@ -47,6 +52,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. /// /// Retransmission can only be achieved if cache is enabled. + #[zenoh_macros::unstable] fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self.session, self.key_expr).sample_miss_detection() } @@ -54,6 +60,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a /// Allow this publisher to be detected by subscribers. /// /// This allows Subscribers to retrieve the local history. + #[zenoh_macros::unstable] fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self.session, self.key_expr).publisher_detection() } diff --git a/zenoh-ext/src/querying_subscriber.rs b/zenoh-ext/src/querying_subscriber.rs index 3935111ee5..9a4943756b 100644 --- a/zenoh-ext/src/querying_subscriber.rs +++ b/zenoh-ext/src/querying_subscriber.rs @@ -60,8 +60,10 @@ impl From for KeySpace { #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct LivelinessSpace; +#[zenoh_macros::unstable] #[allow(deprecated)] impl From for KeySpace { + #[zenoh_macros::unstable] fn from(_: LivelinessSpace) -> Self { KeySpace::Liveliness } @@ -84,9 +86,11 @@ pub struct QueryingSubscriberBuilder<'a, 'b, KeySpace, Handler, const BACKGROUND pub(crate) handler: Handler, } +#[zenoh_macros::unstable] #[allow(deprecated)] impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandler> { /// Add callback to [`FetchingSubscriber`]. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback( @@ -107,6 +111,7 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandle /// /// Subscriber will not be undeclared when dropped, with the callback running /// in background until the session is closed. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback_mut( @@ -120,6 +125,7 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandle } /// Use the given handler to receive Samples. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn with( @@ -156,12 +162,14 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, DefaultHandle } } +#[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] #[allow(deprecated)] impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, Callback> { /// Register the subscriber callback to be run in background until the session is closed. /// /// Background builder doesn't return a `FetchingSubscriber` object anymore. + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn background(self) -> QueryingSubscriberBuilder<'a, 'b, KeySpace, Callback, true> { QueryingSubscriberBuilder { @@ -179,6 +187,7 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, Callback @@ -197,6 +206,7 @@ impl<'b, Handler, const BACKGROUND: bool> } /// Change the selector to be used for queries. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_selector(mut self, query_selector: IntoSelector) -> Self @@ -209,6 +219,7 @@ impl<'b, Handler, const BACKGROUND: bool> } /// Change the target to be used for queries. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_target(mut self, query_target: QueryTarget) -> Self { @@ -217,6 +228,7 @@ impl<'b, Handler, const BACKGROUND: bool> } /// Change the consolidation mode to be used for queries. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_consolidation>( @@ -228,6 +240,7 @@ impl<'b, Handler, const BACKGROUND: bool> } /// Change the accepted replies for queries. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_accept_replies(mut self, accept_replies: ReplyKeyExpr) -> Self { @@ -236,12 +249,14 @@ impl<'b, Handler, const BACKGROUND: bool> } } +#[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] #[allow(deprecated)] impl<'a, 'b, KeySpace, Handler, const BACKGROUND: bool> QueryingSubscriberBuilder<'a, 'b, KeySpace, Handler, BACKGROUND> { /// Change the timeout to be used for queries. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_timeout(mut self, query_timeout: Duration) -> Self { @@ -249,6 +264,7 @@ impl<'a, 'b, KeySpace, Handler, const BACKGROUND: bool> self } + #[zenoh_macros::unstable] #[allow(clippy::type_complexity)] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn into_fetching_subscriber_builder( @@ -309,6 +325,7 @@ impl<'a, 'b, KeySpace, Handler, const BACKGROUND: bool> } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Resolvable for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where @@ -318,6 +335,7 @@ where type To = ZResult>; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Wait for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where @@ -325,11 +343,13 @@ where Handler: IntoHandler + Send, Handler::Handler: Send, { + #[zenoh_macros::unstable] fn wait(self) -> ::To { self.into_fetching_subscriber_builder()?.wait() } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl IntoFuture for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where @@ -340,26 +360,31 @@ where type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Resolvable for QueryingSubscriberBuilder<'_, '_, KeySpace, Callback, true> { type To = ZResult<()>; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl Wait for QueryingSubscriberBuilder<'_, '_, KeySpace, Callback, true> where KeySpace: Into + Clone, { + #[zenoh_macros::unstable] fn wait(self) -> ::To { self.into_fetching_subscriber_builder()?.wait() } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl IntoFuture for QueryingSubscriberBuilder<'_, '_, KeySpace, Callback, true> where @@ -368,6 +393,7 @@ where type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } @@ -458,6 +484,7 @@ pub struct FetchingSubscriberBuilder< pub(crate) phantom: std::marker::PhantomData, } +#[zenoh_macros::unstable] #[allow(deprecated)] impl< 'a, @@ -470,6 +497,7 @@ impl< where TryIntoSample: ExtractSample, { + #[zenoh_macros::unstable] fn with_static_keys( self, ) -> FetchingSubscriberBuilder<'a, 'static, KeySpace, Handler, Fetch, TryIntoSample> { @@ -485,6 +513,7 @@ where } } +#[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] #[allow(deprecated)] impl< @@ -498,6 +527,7 @@ where TryIntoSample: ExtractSample, { /// Add callback to [`FetchingSubscriber`]. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback( @@ -518,6 +548,7 @@ where /// /// Subscriber will not be undeclared when dropped, with the callback running /// in background until the session is closed. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback_mut( @@ -531,6 +562,7 @@ where } /// Use the given handler to receive Samples. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn with( @@ -561,6 +593,7 @@ where } } +#[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] #[allow(deprecated)] impl< @@ -576,6 +609,7 @@ where /// Register the subscriber callback to be run in background until the session is closed. /// /// Background builder doesn't return a `FetchingSubscriber` object anymore. + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn background( self, @@ -593,6 +627,7 @@ where } } +#[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] #[allow(deprecated)] impl< @@ -615,6 +650,7 @@ where } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl< KeySpace, @@ -630,6 +666,7 @@ where type To = ZResult>; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl< KeySpace, @@ -643,11 +680,13 @@ where Handler::Handler: Send, TryIntoSample: ExtractSample + Send + Sync, { + #[zenoh_macros::unstable] fn wait(self) -> ::To { FetchingSubscriber::new(self.with_static_keys()) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl< KeySpace, @@ -664,11 +703,13 @@ where type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl< KeySpace, @@ -682,6 +723,7 @@ where type To = ZResult<()>; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl< KeySpace, @@ -693,6 +735,7 @@ where KeySpace: Into, TryIntoSample: ExtractSample + Send + Sync, { + #[zenoh_macros::unstable] fn wait(self) -> ::To { FetchingSubscriber::new(self.with_static_keys())? .subscriber @@ -701,6 +744,7 @@ where } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl< KeySpace, @@ -715,6 +759,7 @@ where type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } @@ -760,21 +805,26 @@ pub struct FetchingSubscriber { handler: Handler, } +#[zenoh_macros::unstable] #[allow(deprecated)] impl std::ops::Deref for FetchingSubscriber { type Target = Handler; + #[zenoh_macros::unstable] fn deref(&self) -> &Self::Target { &self.handler } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl std::ops::DerefMut for FetchingSubscriber { + #[zenoh_macros::unstable] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.handler } } +#[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] #[allow(deprecated)] impl FetchingSubscriber { @@ -860,12 +910,14 @@ impl FetchingSubscriber { } /// Undeclare this [`FetchingSubscriber`]`. + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn undeclare(self) -> impl Resolve> { self.subscriber.undeclare() } + #[zenoh_macros::unstable] #[zenoh_macros::internal] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn set_background(&mut self, background: bool) { @@ -873,6 +925,7 @@ impl FetchingSubscriber { } /// Return the key expression of this FetchingSubscriber + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn key_expr(&self) -> &KeyExpr<'static> { @@ -915,6 +968,7 @@ impl FetchingSubscriber { /// .unwrap(); /// # } /// ``` + #[zenoh_macros::unstable] #[inline] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn fetch< @@ -1009,6 +1063,7 @@ pub struct FetchBuilder< callback: Callback, } +#[zenoh_macros::unstable] #[allow(deprecated)] impl) -> ZResult<()>, TryIntoSample> Resolvable for FetchBuilder @@ -1018,18 +1073,21 @@ where type To = ZResult<()>; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl) -> ZResult<()>, TryIntoSample> Wait for FetchBuilder where TryIntoSample: ExtractSample, { + #[zenoh_macros::unstable] fn wait(self) -> ::To { let handler = register_handler(self.state, self.callback); run_fetch(self.fetch, handler) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl) -> ZResult<()>, TryIntoSample> IntoFuture for FetchBuilder @@ -1039,6 +1097,7 @@ where type Output = ::To; type IntoFuture = Ready<::To>; + #[zenoh_macros::unstable] fn into_future(self) -> Self::IntoFuture { std::future::ready(self.wait()) } @@ -1050,6 +1109,7 @@ fn register_handler(state: Arc>, callback: Callback) - RepliesHandler { state, callback } } +#[zenoh_macros::unstable] #[allow(deprecated)] fn run_fetch< Fetch: FnOnce(Box) -> ZResult<()>, diff --git a/zenoh-ext/src/session_ext.rs b/zenoh-ext/src/session_ext.rs index 1126707cc3..8daa25e78d 100644 --- a/zenoh-ext/src/session_ext.rs +++ b/zenoh-ext/src/session_ext.rs @@ -22,6 +22,8 @@ use super::PublicationCacheBuilder; #[allow(deprecated)] pub trait SessionExt { // REVIEW(fuzzypixelz): this doc test is the only one to use the programmatic configuration API.. + /// Declare a [`PublicationCache`](crate::PublicationCache). + /// /// Examples: /// ``` /// # #[tokio::main] @@ -40,6 +42,7 @@ pub trait SessionExt { /// # } /// ``` #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] + #[zenoh_macros::unstable] fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( &'a self, pub_key_expr: TryIntoKeyExpr, @@ -51,6 +54,7 @@ pub trait SessionExt { #[allow(deprecated)] impl SessionExt for Session { + #[zenoh_macros::unstable] fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( &'a self, pub_key_expr: TryIntoKeyExpr, diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 50903c0039..3704ab01e3 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -39,6 +39,7 @@ impl<'a, S> SubscriberForward<'a, S> for Subscriber> where S: futures::sink::Sink, { + #[zenoh_macros::unstable] type Output = Forward, fn(Sample) -> Result>, S>; fn forward(&'a mut self, sink: S) -> Self::Output { @@ -86,6 +87,7 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { /// } /// # } /// ``` + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn fetching< Fetch: FnOnce(Box) -> ZResult<()>, @@ -125,6 +127,7 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { /// } /// # } /// ``` + #[zenoh_macros::unstable] #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn querying(self) -> QueryingSubscriberBuilder<'a, 'b, Self::KeySpace, Handler>; } @@ -135,21 +138,26 @@ pub trait AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> { /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that enable caching. + #[zenoh_macros::unstable] fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; /// Ask for retransmission of detected lost Samples. /// /// Retransmission can only be achieved by Publishers that enable /// caching and sample_miss_detection. + #[zenoh_macros::unstable] fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; /// Allow this subscriber to be detected through liveliness. + #[zenoh_macros::unstable] fn subscriber_detection(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; /// Turn this `Subscriber`into an `AdvancedSubscriber`. + #[zenoh_macros::unstable] fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; } +#[zenoh_macros::unstable] #[allow(deprecated)] impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilder<'a, 'b, Handler> { type KeySpace = crate::UserSpace; @@ -187,6 +195,7 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilde /// } /// # } /// ``` + #[zenoh_macros::unstable] fn fetching< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -236,6 +245,7 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilde /// } /// # } /// ``` + #[zenoh_macros::unstable] fn querying(self) -> QueryingSubscriberBuilder<'a, 'b, Self::KeySpace, Handler> { QueryingSubscriberBuilder { session: self.session, @@ -255,12 +265,14 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilde } } +#[zenoh_macros::unstable] impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> for SubscriberBuilder<'a, 'b, Handler> { /// Enable query for historical data. /// /// History can only be retransmitted by Publishers that enable caching. + #[zenoh_macros::unstable] fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) .history(config) @@ -270,23 +282,27 @@ impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> /// /// Retransmission can only be achieved by Publishers that enable /// caching and sample_miss_detection. + #[zenoh_macros::unstable] fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) .recovery(conf) } /// Allow this subscriber to be detected through liveliness. + #[zenoh_macros::unstable] fn subscriber_detection(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) .subscriber_detection() } /// Turn this `Subscriber`into an `AdvancedSubscriber`. + #[zenoh_macros::unstable] fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) } } +#[zenoh_macros::unstable] #[allow(deprecated)] impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for LivelinessSubscriberBuilder<'a, 'b, Handler> @@ -329,6 +345,7 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> /// } /// # } /// ``` + #[zenoh_macros::unstable] fn fetching< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -380,6 +397,7 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> /// } /// # } /// ``` + #[zenoh_macros::unstable] fn querying(self) -> QueryingSubscriberBuilder<'a, 'b, Self::KeySpace, Handler> { QueryingSubscriberBuilder { session: self.session, From ab05e1edaf313a576675440844e632f09e5a7ed8 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 11:25:13 +0100 Subject: [PATCH 63/88] Add missing AdvancedSubscriber methods --- zenoh-ext/src/advanced_subscriber.rs | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 6e4f821b29..e88dd8b175 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -903,6 +903,45 @@ impl AdvancedSubscriber { Ok(reliable_subscriber) } + /// Returns the [`EntityGlobalId`] of this AdvancedSubscriber. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); + /// let subscriber = session.declare_subscriber("key/expression") + /// .advanced() + /// .await + /// .unwrap(); + /// let subscriber_id = subscriber.id(); + /// # } + /// ``` + #[zenoh_macros::unstable] + pub fn id(&self) -> EntityGlobalId { + self._subscriber.id() + } + + /// Returns the [`KeyExpr`] this subscriber subscribes to. + pub fn key_expr(&self) -> &KeyExpr<'static> { + self._subscriber.key_expr() + } + + /// Returns a reference to this subscriber's handler. + /// An handler is anything that implements [`zenoh::handlers::IntoHandler`]. + /// The default handler is [`zenoh::handlers::DefaultHandler`]. + pub fn handler(&self) -> &Handler { + &self.receiver + } + + /// Returns a mutable reference to this subscriber's handler. + /// An handler is anything that implements [`zenoh::handlers::IntoHandler`]. + /// The default handler is [`zenoh::handlers::DefaultHandler`]. + pub fn handler_mut(&mut self) -> &mut Handler { + &mut self.receiver + } + #[zenoh_macros::unstable] pub fn sample_miss_listener(&self) -> SampleMissListenerBuilder<'_, DefaultHandler> { SampleMissListenerBuilder { From 1bae9453e0673bbbabcb6d998e8bebc935273584 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 12:00:48 +0100 Subject: [PATCH 64/88] Fix WeakSession::Session internal function --- Cargo.lock | 21 +++++++++++++++++++++ Cargo.toml | 1 + zenoh/Cargo.toml | 1 + zenoh/src/api/publisher.rs | 2 +- zenoh/src/api/session.rs | 13 +++++++++++-- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee66c75767..8363b46684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3065,6 +3065,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "regex" version = "1.10.6" @@ -5071,6 +5091,7 @@ dependencies = [ "petgraph", "phf", "rand 0.8.5", + "ref-cast", "rustc_version 0.4.1", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index de23d2eea4..b69d42f66e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,6 +136,7 @@ quote = "1.0.37" rand = { version = "0.8.5", default-features = false } # Default features are disabled due to usage in no_std crates rand_chacha = "0.3.1" rcgen = "0.13.1" +ref-cast = "1.0.23" regex = "1.10.6" ron = "0.8.1" ringbuffer-spsc = "0.1.9" diff --git a/zenoh/Cargo.toml b/zenoh/Cargo.toml index ece72ab5c4..4f50deefd2 100644 --- a/zenoh/Cargo.toml +++ b/zenoh/Cargo.toml @@ -84,6 +84,7 @@ paste = { workspace = true } petgraph = { workspace = true } phf = { workspace = true } rand = { workspace = true, features = ["default"] } +ref-cast = { workspace = true } serde = { workspace = true, features = ["default"] } serde_json = { workspace = true } socket2 = { workspace = true } diff --git a/zenoh/src/api/publisher.rs b/zenoh/src/api/publisher.rs index 24d075b22e..2b2f21d8b0 100644 --- a/zenoh/src/api/publisher.rs +++ b/zenoh/src/api/publisher.rs @@ -315,7 +315,7 @@ impl<'a> Publisher<'a> { } #[zenoh_macros::internal] - pub fn session(&self) -> crate::Session { + pub fn session(&self) -> &crate::Session { self.session.session() } } diff --git a/zenoh/src/api/session.rs b/zenoh/src/api/session.rs index d22d52ee25..294427426f 100644 --- a/zenoh/src/api/session.rs +++ b/zenoh/src/api/session.rs @@ -26,6 +26,9 @@ use std::{ }; use async_trait::async_trait; +#[zenoh_macros::internal] +use ref_cast::ref_cast_custom; +use ref_cast::RefCastCustom; use tracing::{error, info, trace, warn}; use uhlc::Timestamp; #[cfg(feature = "internal")] @@ -553,12 +556,18 @@ impl fmt::Debug for SessionInner { /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// session.put("key/expression", "value").await.unwrap(); /// # } +#[derive(RefCastCustom)] +#[repr(transparent)] pub struct Session(pub(crate) Arc); impl Session { pub(crate) fn downgrade(&self) -> WeakSession { WeakSession::new(&self.0) } + + #[cfg(feature = "internal")] + #[ref_cast_custom] + pub(crate) const fn ref_cast(from: &Arc) -> &Self; } impl fmt::Debug for Session { @@ -610,8 +619,8 @@ impl WeakSession { } #[zenoh_macros::internal] - pub(crate) fn session(&self) -> Session { - Session(self.0.clone()) + pub(crate) fn session(&self) -> &Session { + Session::ref_cast(&self.0) } } From 5ca2e5031435ab7b0a5d1226ee3f78351edf6ad5 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 12:16:05 +0100 Subject: [PATCH 65/88] Expose missing SampleMissListener and related structs --- zenoh-ext/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index 850090b02c..d783fa1671 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -44,6 +44,7 @@ pub use crate::{ advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder}, advanced_subscriber::{ AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, RecoveryConfig, + SampleMissHandlerUndeclaration, SampleMissListener, SampleMissListenerBuilder, }, publication_cache::{PublicationCache, PublicationCacheBuilder}, publisher_ext::AdvancedPublisherBuilderExt, From 7c374c49b5daf283edf9856a156e0b6bead8da5d Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 12:21:22 +0100 Subject: [PATCH 66/88] Add AdvancedPublisherBuilderExt::advanced function --- zenoh-ext/src/publisher_ext.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index f1ba1698d1..188ead1c33 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -36,6 +36,10 @@ pub trait AdvancedPublisherBuilderExt<'a, 'b, 'c> { /// This allows Subscribers to retrieve the local history. #[zenoh_macros::unstable] fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; + + /// Turn this `Publisher` into an `AdvancedPublisher`. + #[zenoh_macros::unstable] + fn advanced(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; } #[zenoh_macros::unstable] @@ -64,4 +68,10 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self.session, self.key_expr).publisher_detection() } + + /// Turn this `Publisher` into an `AdvancedPublisher`. + #[zenoh_macros::unstable] + fn advanced(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { + AdvancedPublisherBuilder::new(self.session, self.key_expr) + } } From 0ffb13378dba08ecf284db9d82aef4893c98590c Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 12:35:31 +0100 Subject: [PATCH 67/88] Add missing AdvancedPublisherBuilder functions --- zenoh-ext/src/advanced_publisher.rs | 49 +++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 2f2ade7bd3..1a6d19ebb1 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -25,8 +25,8 @@ use zenoh::{ key_expr::KeyExpr, liveliness::LivelinessToken, pubsub::{PublicationBuilder, PublicationBuilderDelete, PublicationBuilderPut, Publisher}, - qos::{CongestionControl, Priority}, - sample::SourceInfo, + qos::{CongestionControl, Priority, Reliability}, + sample::{Locality, SourceInfo}, session::EntityGlobalId, Resolvable, Resolve, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_EMPTY, }; @@ -47,6 +47,9 @@ pub(crate) enum Sequencing { pub struct AdvancedPublisherBuilder<'a, 'b, 'c> { session: &'a Session, pub_key_expr: ZResult>, + encoding: Encoding, + destination: Locality, + reliability: Reliability, meta_key_expr: Option>>, sequencing: Sequencing, liveliness: bool, @@ -64,6 +67,9 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder { session, pub_key_expr, + encoding: Encoding::default(), + destination: Locality::default(), + reliability: Reliability::default(), meta_key_expr: None, sequencing: Sequencing::None, liveliness: false, @@ -72,6 +78,31 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { } } + /// Changes the [`zenoh::sample::Locality`] applied when routing the data. + /// + /// This restricts the matching subscribers that will receive the published data to the ones + /// that have the given [`zenoh::sample::Locality`]. + #[zenoh_macros::unstable] + #[inline] + pub fn allowed_destination(mut self, destination: Locality) -> Self { + self.destination = destination; + self + } + + /// Changes the [`zenoh::qos::Reliability`] to apply when routing the data. + /// + /// **NOTE**: Currently `reliability` does not trigger any data retransmission on the wire. It + /// is rather used as a marker on the wire and it may be used to select the best link + /// available (e.g. TCP for reliable data and UDP for best effort data). + #[zenoh_macros::unstable] + #[inline] + pub fn reliability(self, reliability: Reliability) -> Self { + Self { + reliability, + ..self + } + } + /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. /// /// Retransmission can only be achieved if history is enabled. @@ -115,6 +146,17 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { } } +#[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] +impl EncodingBuilderTrait for AdvancedPublisherBuilder<'_, '_, '_> { + fn encoding>(self, encoding: T) -> Self { + Self { + encoding: encoding.into(), + ..self + } + } +} + #[zenoh_macros::unstable] impl<'a> Resolvable for AdvancedPublisherBuilder<'a, '_, '_> { type To = ZResult>; @@ -161,6 +203,9 @@ impl<'a> AdvancedPublisher<'a> { let publisher = conf .session .declare_publisher(key_expr.clone().into_owned()) + .encoding(conf.encoding) + .allowed_destination(conf.destination) + .reliability(conf.reliability) .wait()?; let id = publisher.id(); let prefix = KE_ADV_PREFIX / &id.zid().into_keyexpr(); From dc9200c189acce95d356309c851c6bad443dd729 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 14:39:07 +0100 Subject: [PATCH 68/88] Fix doctests --- zenoh-ext/src/advanced_subscriber.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index e88dd8b175..1e25c42cb9 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -909,6 +909,7 @@ impl AdvancedSubscriber { /// ``` /// # #[tokio::main] /// # async fn main() { + /// use zenoh_ext::AdvancedSubscriberBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let subscriber = session.declare_subscriber("key/expression") From 6e9f9b932c2958ec215f39a09d5b970cc5a21842 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 14:41:40 +0100 Subject: [PATCH 69/88] Expose Miss struct --- zenoh-ext/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index d783fa1671..e8fbef6b4c 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -43,7 +43,7 @@ pub use crate::{ advanced_cache::{CacheConfig, QoS}, advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder}, advanced_subscriber::{ - AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, RecoveryConfig, + AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, Miss, RecoveryConfig, SampleMissHandlerUndeclaration, SampleMissListener, SampleMissListenerBuilder, }, publication_cache::{PublicationCache, PublicationCacheBuilder}, From d3a78a038a324deb7b88434abe044235661ae16c Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 14:57:57 +0100 Subject: [PATCH 70/88] impl QoSBuilderTrait for AdvancedPublisherBuilder --- zenoh-ext/src/advanced_publisher.rs | 44 ++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 1a6d19ebb1..5b2b936048 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -20,7 +20,9 @@ use zenoh::{ bytes::{Encoding, OptionZBytes, ZBytes}, internal::{ bail, - traits::{EncodingBuilderTrait, SampleBuilderTrait, TimestampBuilderTrait}, + traits::{ + EncodingBuilderTrait, QoSBuilderTrait, SampleBuilderTrait, TimestampBuilderTrait, + }, }, key_expr::KeyExpr, liveliness::LivelinessToken, @@ -50,6 +52,9 @@ pub struct AdvancedPublisherBuilder<'a, 'b, 'c> { encoding: Encoding, destination: Locality, reliability: Reliability, + congestion_control: CongestionControl, + priority: Priority, + is_express: bool, meta_key_expr: Option>>, sequencing: Sequencing, liveliness: bool, @@ -70,6 +75,9 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { encoding: Encoding::default(), destination: Locality::default(), reliability: Reliability::default(), + congestion_control: CongestionControl::default(), + priority: Priority::default(), + is_express: false, meta_key_expr: None, sequencing: Sequencing::None, liveliness: false, @@ -157,6 +165,37 @@ impl EncodingBuilderTrait for AdvancedPublisherBuilder<'_, '_, '_> { } } +#[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] +impl QoSBuilderTrait for AdvancedPublisherBuilder<'_, '_, '_> { + /// Changes the [`zenoh::qos::CongestionControl`] to apply when routing the data. + #[inline] + #[zenoh_macros::unstable] + fn congestion_control(self, congestion_control: CongestionControl) -> Self { + Self { + congestion_control, + ..self + } + } + + /// Changes the [`zenoh::qos::Priority`] of the written data. + #[inline] + #[zenoh_macros::unstable] + fn priority(self, priority: Priority) -> Self { + Self { priority, ..self } + } + + /// Changes the Express policy to apply when routing the data. + /// + /// When express is set to `true`, then the message will not be batched. + /// This usually has a positive impact on latency but negative impact on throughput. + #[inline] + #[zenoh_macros::unstable] + fn express(self, is_express: bool) -> Self { + Self { is_express, ..self } + } +} + #[zenoh_macros::unstable] impl<'a> Resolvable for AdvancedPublisherBuilder<'a, '_, '_> { type To = ZResult>; @@ -206,6 +245,9 @@ impl<'a> AdvancedPublisher<'a> { .encoding(conf.encoding) .allowed_destination(conf.destination) .reliability(conf.reliability) + .congestion_control(conf.congestion_control) + .priority(conf.priority) + .express(conf.is_express) .wait()?; let id = publisher.id(); let prefix = KE_ADV_PREFIX / &id.zid().into_keyexpr(); From 7332d02da523177405d51b724224f1134332fed3 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 17:57:56 +0100 Subject: [PATCH 71/88] Propagate PublisherBuilder values to AdvancedPublisherBuilder --- zenoh-ext/src/advanced_publisher.rs | 26 +++++++++++++------------- zenoh-ext/src/advanced_subscriber.rs | 16 ++++++---------- zenoh-ext/src/publisher_ext.rs | 8 ++++---- zenoh-ext/src/subscriber_ext.rs | 11 ++++------- zenoh/src/api/builders/publisher.rs | 19 +++++++++++++++++++ 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 5b2b936048..89bd353a7d 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -26,7 +26,10 @@ use zenoh::{ }, key_expr::KeyExpr, liveliness::LivelinessToken, - pubsub::{PublicationBuilder, PublicationBuilderDelete, PublicationBuilderPut, Publisher}, + pubsub::{ + PublicationBuilder, PublicationBuilderDelete, PublicationBuilderPut, Publisher, + PublisherBuilder, + }, qos::{CongestionControl, Priority, Reliability}, sample::{Locality, SourceInfo}, session::EntityGlobalId, @@ -65,19 +68,16 @@ pub struct AdvancedPublisherBuilder<'a, 'b, 'c> { #[zenoh_macros::unstable] impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { #[zenoh_macros::unstable] - pub(crate) fn new( - session: &'a Session, - pub_key_expr: ZResult>, - ) -> AdvancedPublisherBuilder<'a, 'b, 'c> { + pub(crate) fn new(builder: PublisherBuilder<'a, 'b>) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder { - session, - pub_key_expr, - encoding: Encoding::default(), - destination: Locality::default(), - reliability: Reliability::default(), - congestion_control: CongestionControl::default(), - priority: Priority::default(), - is_express: false, + session: builder.session, + pub_key_expr: builder.key_expr, + encoding: builder.encoding, + destination: builder.destination, + reliability: builder.reliability, + congestion_control: builder.congestion_control, + priority: builder.priority, + is_express: builder.is_express, meta_key_expr: None, sequencing: Sequencing::None, liveliness: false, diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 1e25c42cb9..2b999feb60 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -18,6 +18,7 @@ use zenoh::{ handlers::{Callback, IntoHandler}, key_expr::KeyExpr, liveliness::LivelinessToken, + pubsub::SubscriberBuilder, query::{ ConsolidationMode, Parameters, Selector, TimeBound, TimeExpr, TimeRange, ZenohParameters, }, @@ -132,17 +133,12 @@ pub struct AdvancedSubscriberBuilder<'a, 'b, 'c, Handler, const BACKGROUND: bool #[zenoh_macros::unstable] impl<'a, 'b, Handler> AdvancedSubscriberBuilder<'a, 'b, '_, Handler> { #[zenoh_macros::unstable] - pub(crate) fn new( - session: &'a Session, - key_expr: ZResult>, - origin: Locality, - handler: Handler, - ) -> Self { + pub(crate) fn new(builder: SubscriberBuilder<'a, 'b, Handler>) -> Self { AdvancedSubscriberBuilder { - session, - key_expr, - origin, - handler, + session: builder.session, + key_expr: builder.key_expr, + origin: builder.origin, + handler: builder.handler, retransmission: None, query_target: QueryTarget::All, query_timeout: Duration::from_secs(10), diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index 188ead1c33..2fa5878404 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -50,7 +50,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a /// Retransmission can only be achieved if history is enabled. #[zenoh_macros::unstable] fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c> { - AdvancedPublisherBuilder::new(self.session, self.key_expr).cache(config) + AdvancedPublisherBuilder::new(self).cache(config) } /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. @@ -58,7 +58,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a /// Retransmission can only be achieved if cache is enabled. #[zenoh_macros::unstable] fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { - AdvancedPublisherBuilder::new(self.session, self.key_expr).sample_miss_detection() + AdvancedPublisherBuilder::new(self).sample_miss_detection() } /// Allow this publisher to be detected by subscribers. @@ -66,12 +66,12 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a /// This allows Subscribers to retrieve the local history. #[zenoh_macros::unstable] fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { - AdvancedPublisherBuilder::new(self.session, self.key_expr).publisher_detection() + AdvancedPublisherBuilder::new(self).publisher_detection() } /// Turn this `Publisher` into an `AdvancedPublisher`. #[zenoh_macros::unstable] fn advanced(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { - AdvancedPublisherBuilder::new(self.session, self.key_expr) + AdvancedPublisherBuilder::new(self) } } diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index 3704ab01e3..badd9b36bc 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -274,8 +274,7 @@ impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> /// History can only be retransmitted by Publishers that enable caching. #[zenoh_macros::unstable] fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { - AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .history(config) + AdvancedSubscriberBuilder::new(self).history(config) } /// Ask for retransmission of detected lost Samples. @@ -284,21 +283,19 @@ impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> /// caching and sample_miss_detection. #[zenoh_macros::unstable] fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { - AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .recovery(conf) + AdvancedSubscriberBuilder::new(self).recovery(conf) } /// Allow this subscriber to be detected through liveliness. #[zenoh_macros::unstable] fn subscriber_detection(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { - AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) - .subscriber_detection() + AdvancedSubscriberBuilder::new(self).subscriber_detection() } /// Turn this `Subscriber`into an `AdvancedSubscriber`. #[zenoh_macros::unstable] fn advanced(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { - AdvancedSubscriberBuilder::new(self.session, self.key_expr, self.origin, self.handler) + AdvancedSubscriberBuilder::new(self) } } diff --git a/zenoh/src/api/builders/publisher.rs b/zenoh/src/api/builders/publisher.rs index c7eed2770e..689c12a96d 100644 --- a/zenoh/src/api/builders/publisher.rs +++ b/zenoh/src/api/builders/publisher.rs @@ -293,12 +293,31 @@ pub struct PublisherBuilder<'a, 'b> { #[cfg(not(feature = "internal"))] pub(crate) key_expr: ZResult>, + #[cfg(feature = "internal")] + pub encoding: Encoding, + #[cfg(not(feature = "internal"))] pub(crate) encoding: Encoding, + #[cfg(feature = "internal")] + pub congestion_control: CongestionControl, + #[cfg(not(feature = "internal"))] pub(crate) congestion_control: CongestionControl, + #[cfg(feature = "internal")] + pub priority: Priority, + #[cfg(not(feature = "internal"))] pub(crate) priority: Priority, + #[cfg(feature = "internal")] + pub is_express: bool, + #[cfg(not(feature = "internal"))] pub(crate) is_express: bool, + #[cfg(feature = "internal")] + #[cfg(feature = "unstable")] + pub reliability: Reliability, + #[cfg(not(feature = "internal"))] #[cfg(feature = "unstable")] pub(crate) reliability: Reliability, + #[cfg(feature = "internal")] + pub destination: Locality, + #[cfg(not(feature = "internal"))] pub(crate) destination: Locality, } From b63c08017783cc6d8a5459e86442eae6fed4fd35 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 18:00:52 +0100 Subject: [PATCH 72/88] Rename AdvancedSubscriber::close() --- zenoh-ext/src/advanced_subscriber.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 2b999feb60..390fd745bb 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -947,10 +947,10 @@ impl AdvancedSubscriber { } } - /// Close this AdvancedSubscriber + /// Undeclares this AdvancedSubscriber #[inline] #[zenoh_macros::unstable] - pub fn close(self) -> impl Resolve> { + pub fn undeclare(self) -> impl Resolve> { self._subscriber.undeclare() } } From 4aaca6e1477bf0162a2db14a72720e7195318e93 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 18:03:57 +0100 Subject: [PATCH 73/88] Add unstable tags --- zenoh-ext/src/advanced_subscriber.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 390fd745bb..13eefe8eb2 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -921,6 +921,7 @@ impl AdvancedSubscriber { } /// Returns the [`KeyExpr`] this subscriber subscribes to. + #[zenoh_macros::unstable] pub fn key_expr(&self) -> &KeyExpr<'static> { self._subscriber.key_expr() } @@ -928,6 +929,7 @@ impl AdvancedSubscriber { /// Returns a reference to this subscriber's handler. /// An handler is anything that implements [`zenoh::handlers::IntoHandler`]. /// The default handler is [`zenoh::handlers::DefaultHandler`]. + #[zenoh_macros::unstable] pub fn handler(&self) -> &Handler { &self.receiver } @@ -935,6 +937,7 @@ impl AdvancedSubscriber { /// Returns a mutable reference to this subscriber's handler. /// An handler is anything that implements [`zenoh::handlers::IntoHandler`]. /// The default handler is [`zenoh::handlers::DefaultHandler`]. + #[zenoh_macros::unstable] pub fn handler_mut(&mut self) -> &mut Handler { &mut self.receiver } From a8bb82f8e1d1fb54be542dcf9b1f714a039a6748 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 18:11:11 +0100 Subject: [PATCH 74/88] Add AdvancedSubscriber::detect_publishers function --- zenoh-ext/src/advanced_subscriber.rs | 20 ++++++++++++++------ zenoh/src/api/subscriber.rs | 5 +++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 13eefe8eb2..c253a98e17 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -17,7 +17,7 @@ use zenoh::{ config::ZenohId, handlers::{Callback, IntoHandler}, key_expr::KeyExpr, - liveliness::LivelinessToken, + liveliness::{LivelinessSubscriberBuilder, LivelinessToken}, pubsub::SubscriberBuilder, query::{ ConsolidationMode, Parameters, Selector, TimeBound, TimeExpr, TimeRange, ZenohParameters, @@ -395,7 +395,7 @@ struct SourceState { #[zenoh_macros::unstable] pub struct AdvancedSubscriber { statesref: Arc>, - _subscriber: Subscriber<()>, + subscriber: Subscriber<()>, receiver: Receiver, _liveliness_subscriber: Option>, _token: Option, @@ -890,7 +890,7 @@ impl AdvancedSubscriber { let reliable_subscriber = AdvancedSubscriber { statesref, - _subscriber: subscriber, + subscriber, receiver, _liveliness_subscriber: liveliness_subscriber, _token: token, @@ -917,13 +917,13 @@ impl AdvancedSubscriber { /// ``` #[zenoh_macros::unstable] pub fn id(&self) -> EntityGlobalId { - self._subscriber.id() + self.subscriber.id() } /// Returns the [`KeyExpr`] this subscriber subscribes to. #[zenoh_macros::unstable] pub fn key_expr(&self) -> &KeyExpr<'static> { - self._subscriber.key_expr() + self.subscriber.key_expr() } /// Returns a reference to this subscriber's handler. @@ -950,11 +950,19 @@ impl AdvancedSubscriber { } } + #[zenoh_macros::unstable] + pub fn detect_publishers(&self) -> LivelinessSubscriberBuilder<'_, '_, DefaultHandler> { + self.subscriber + .session() + .liveliness() + .declare_subscriber(KE_ADV_PREFIX / KE_STARSTAR / KE_AT / self.subscriber.key_expr()) + } + /// Undeclares this AdvancedSubscriber #[inline] #[zenoh_macros::unstable] pub fn undeclare(self) -> impl Resolve> { - self._subscriber.undeclare() + self.subscriber.undeclare() } } diff --git a/zenoh/src/api/subscriber.rs b/zenoh/src/api/subscriber.rs index 099a1d174a..094757f8d2 100644 --- a/zenoh/src/api/subscriber.rs +++ b/zenoh/src/api/subscriber.rs @@ -221,6 +221,11 @@ impl Subscriber { pub fn set_background(&mut self, background: bool) { self.inner.undeclare_on_drop = !background; } + + #[zenoh_macros::internal] + pub fn session(&self) -> &crate::Session { + self.inner.session.session() + } } impl Drop for Subscriber { From 2df37bc5a7d754e7031617e04ba97ae2e2fd243c Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 20:42:01 +0100 Subject: [PATCH 75/88] Remove debug println --- zenoh/src/api/admin.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/zenoh/src/api/admin.rs b/zenoh/src/api/admin.rs index b0de76ccf2..3f6f032009 100644 --- a/zenoh/src/api/admin.rs +++ b/zenoh/src/api/admin.rs @@ -128,8 +128,6 @@ pub(crate) fn on_admin_query(session: &WeakSession, prefix: &keyexpr, query: Que } } - println!("on admin query"); - if let Ok(own_zid) = keyexpr::new(&session.zid().to_string()) { for transport in zenoh_runtime::ZRuntime::Net .block_in_place(session.runtime.manager().get_transports_unicast()) From fe51436978acff6ddb40e545d2a1cf9f055674b6 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 20:45:31 +0100 Subject: [PATCH 76/88] Renaming --- zenoh-ext/src/advanced_cache.rs | 28 ++++++++++++++-------------- zenoh-ext/src/lib.rs | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index dcc46e5207..2f9e991997 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -37,16 +37,16 @@ kedefine!( ); #[zenoh_macros::unstable] -/// Configure replies QoS. +/// Configure replies. #[derive(Clone, Debug)] -pub struct QoS { +pub struct RepliesConfig { priority: Priority, congestion_control: CongestionControl, is_express: bool, } #[zenoh_macros::unstable] -impl Default for QoS { +impl Default for RepliesConfig { fn default() -> Self { Self { priority: Priority::Data, @@ -58,7 +58,7 @@ impl Default for QoS { #[zenoh_macros::internal_trait] #[zenoh_macros::unstable] -impl QoSBuilderTrait for QoS { +impl QoSBuilderTrait for RepliesConfig { #[allow(unused_mut)] #[zenoh_macros::unstable] fn congestion_control(mut self, congestion_control: CongestionControl) -> Self { @@ -86,7 +86,7 @@ impl QoSBuilderTrait for QoS { #[zenoh_macros::unstable] pub struct CacheConfig { max_samples: usize, - replies_qos: QoS, + replies_config: RepliesConfig, } #[zenoh_macros::unstable] @@ -94,7 +94,7 @@ impl Default for CacheConfig { fn default() -> Self { Self { max_samples: 1, - replies_qos: QoS::default(), + replies_config: RepliesConfig::default(), } } } @@ -110,8 +110,8 @@ impl CacheConfig { /// The QoS to apply to replies. #[zenoh_macros::unstable] - pub fn replies_qos(mut self, qos: QoS) -> Self { - self.replies_qos = qos; + pub fn replies_config(mut self, qos: RepliesConfig) -> Self { + self.replies_config = qos; self } } @@ -277,10 +277,10 @@ impl AdvancedCache { .reply_sample( SampleBuilder::from(sample.clone()) .congestion_control( - conf.history.replies_qos.congestion_control, + conf.history.replies_config.congestion_control, ) - .priority(conf.history.replies_qos.priority) - .express(conf.history.replies_qos.is_express) + .priority(conf.history.replies_config.priority) + .express(conf.history.replies_config.is_express) .into(), ) .wait() @@ -304,10 +304,10 @@ impl AdvancedCache { .reply_sample( SampleBuilder::from(sample.clone()) .congestion_control( - conf.history.replies_qos.congestion_control, + conf.history.replies_config.congestion_control, ) - .priority(conf.history.replies_qos.priority) - .express(conf.history.replies_qos.is_express) + .priority(conf.history.replies_config.priority) + .express(conf.history.replies_config.is_express) .into(), ) .wait() diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index e8fbef6b4c..2d73bd16c7 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -40,7 +40,7 @@ pub use crate::serialization::{ #[cfg(feature = "unstable")] #[allow(deprecated)] pub use crate::{ - advanced_cache::{CacheConfig, QoS}, + advanced_cache::{CacheConfig, RepliesConfig}, advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder}, advanced_subscriber::{ AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, Miss, RecoveryConfig, From 0bc723396b65629d7a18e8c7c8bd593fb04f99b5 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 21:46:20 +0100 Subject: [PATCH 77/88] Add unstable tags --- zenoh-ext/src/advanced_publisher.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 89bd353a7d..0013ad6343 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -157,6 +157,7 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { #[zenoh_macros::internal_trait] #[zenoh_macros::unstable] impl EncodingBuilderTrait for AdvancedPublisherBuilder<'_, '_, '_> { + #[zenoh_macros::unstable] fn encoding>(self, encoding: T) -> Self { Self { encoding: encoding.into(), From 00963f33637dda2a13a88098b7857a2c262d9f49 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Mon, 9 Dec 2024 21:46:31 +0100 Subject: [PATCH 78/88] Use std Range --- zenoh-ext/src/advanced_cache.rs | 54 ++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index 2f9e991997..b3d6d4b661 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -14,6 +14,7 @@ use std::{ collections::VecDeque, future::{IntoFuture, Ready}, + ops::{Bound, RangeBounds}, sync::{Arc, RwLock}, }; @@ -187,29 +188,24 @@ impl IntoFuture for AdvancedCacheBuilder<'_, '_, '_> { } #[zenoh_macros::unstable] -fn decode_range(range: &str) -> (Option, Option) { +fn decode_range(range: &str) -> (Bound, Bound) { let mut split = range.split(".."); - let start = split.next().and_then(|s| s.parse::().ok()); - let end = split.next().map(|s| s.parse::().ok()).unwrap_or(start); + let start = split + .next() + .and_then(|s| s.parse::().ok().map(Bound::Included)) + .unwrap_or(Bound::Unbounded); + let end = split + .next() + .map(|s| { + s.parse::() + .ok() + .map(Bound::Included) + .unwrap_or(Bound::Unbounded) + }) + .unwrap_or(start); (start, end) } -#[zenoh_macros::unstable] -fn sample_in_range(sample: &Sample, start: Option, end: Option) -> bool { - if start.is_none() && end.is_none() { - true - } else if let Some(source_sn) = sample.source_info().source_sn() { - match (start, end) { - (Some(start), Some(end)) => source_sn >= start && source_sn <= end, - (Some(start), None) => source_sn >= start, - (None, Some(end)) => source_sn <= end, - (None, None) => true, - } - } else { - false - } -} - /// [`AdvancedCache`]. #[zenoh_macros::unstable] pub struct AdvancedCache { @@ -235,7 +231,7 @@ impl AdvancedCache { &key_expr, conf.history, ); - let cache = Arc::new(RwLock::new(VecDeque::new())); + let cache = Arc::new(RwLock::new(VecDeque::::new())); // declare the queryable that will answer to queries on cache let queryable = conf @@ -245,11 +241,11 @@ impl AdvancedCache { .callback({ let cache = cache.clone(); move |query| { - let (start, end) = query + let range = query .parameters() .get("_sn") .map(decode_range) - .unwrap_or((None, None)); + .unwrap_or((Bound::Unbounded, Bound::Unbounded)); let max = query .parameters() .get("_max") @@ -258,7 +254,12 @@ impl AdvancedCache { if let Some(max) = max { let mut samples = VecDeque::new(); for sample in queue.iter() { - if sample_in_range(sample, start, end) { + if range == (Bound::Unbounded, Bound::Unbounded) + || sample + .source_info() + .source_sn() + .is_some_and(|sn| range.contains(&sn)) + { if let (Some(Ok(time_range)), Some(timestamp)) = (query.parameters().time_range(), sample.timestamp()) { @@ -290,7 +291,12 @@ impl AdvancedCache { } } else { for sample in queue.iter() { - if sample_in_range(sample, start, end) { + if range == (Bound::Unbounded, Bound::Unbounded) + || sample + .source_info() + .source_sn() + .is_some_and(|sn| range.contains(&sn)) + { if let (Some(Ok(time_range)), Some(timestamp)) = (query.parameters().time_range(), sample.timestamp()) { From 3c6ba616780485872bccc090e0dd39cd668fc6b3 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 10:54:36 +0100 Subject: [PATCH 79/88] Spawn Timer in a tokio runtime --- zenoh-ext/src/advanced_subscriber.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index c253a98e17..5108c65d98 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -38,7 +38,7 @@ use { std::time::Duration, uhlc::ID, zenoh::handlers::{locked, DefaultHandler}, - zenoh::internal::zlock, + zenoh::internal::{runtime::ZRuntime, zlock}, zenoh::pubsub::Subscriber, zenoh::query::{QueryTarget, Reply, ReplyKeyExpr}, zenoh::time::Timestamp, @@ -570,6 +570,7 @@ impl AdvancedSubscriber { global_pending_queries: if conf.history.is_some() { 1 } else { 0 }, session, period: retransmission.as_ref().and_then(|r| { + let _rt = ZRuntime::Application.enter(); r.periodic_queries.map(|p| Period { timer: Timer::new(false), period: p, From 18682ff35ca977e8046fb62e643d749bace01087 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 11:23:24 +0100 Subject: [PATCH 80/88] Fix panic when last_delivered is None --- zenoh-ext/src/advanced_subscriber.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 5108c65d98..c9d01b8bde 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -515,7 +515,7 @@ impl Timed for PeriodicQuery { / KE_STARSTAR / KE_AT / &states.key_expr; - let seq_num_range = seq_num_range(Some(state.last_delivered.unwrap() + 1), None); + let seq_num_range = seq_num_range(state.last_delivered.map(|s| s + 1), None); let session = states.session.clone(); let key_expr = states.key_expr.clone().into_owned(); @@ -613,7 +613,7 @@ impl AdvancedSubscriber { / KE_AT / &key_expr; let seq_num_range = - seq_num_range(Some(state.last_delivered.unwrap() + 1), None); + seq_num_range(state.last_delivered.map(|s| s + 1), None); drop(lock); let handler = SequencedRepliesHandler { source_id, From 24e1edeafbf6eaa52689e20701aeea3e067c943b Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 14:53:32 +0100 Subject: [PATCH 81/88] Release lock before calling get --- zenoh-ext/src/advanced_subscriber.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index c9d01b8bde..9c197d3fa4 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -701,7 +701,8 @@ impl AdvancedSubscriber { // TODO : If we already have a state associated to this discovered source // we should query with the appropriate range to avoid unnecessary retransmissions if parsed.eid() == KE_UHLC { - let states = &mut *zlock!(statesref); + let mut lock = zlock!(statesref); + let states = &mut *lock; let entry = states.timestamped_states.entry(ID::from(zid)); let state = entry.or_insert(SourceState:: { last_delivered: None, @@ -709,6 +710,7 @@ impl AdvancedSubscriber { pending_samples: BTreeMap::new(), }); state.pending_queries += 1; + drop(lock); let handler = TimestampedRepliesHandler { id: ID::from(zid), @@ -750,7 +752,8 @@ impl AdvancedSubscriber { EntityId::from_str(parsed.eid().as_str()) { let source_id = EntityGlobalId::new(zid, eid); - let states = &mut *zlock!(statesref); + let mut lock = zlock!(statesref); + let states = &mut *lock; let entry = states.sequenced_states.entry(source_id); let new = matches!(&entry, Entry::Vacant(_)); let state = entry.or_insert(SourceState:: { @@ -759,6 +762,7 @@ impl AdvancedSubscriber { pending_samples: BTreeMap::new(), }); state.pending_queries += 1; + drop(lock); let handler = SequencedRepliesHandler { source_id, @@ -798,15 +802,17 @@ impl AdvancedSubscriber { if new { spawn_periodoic_queries!( - states, + zlock!(statesref), source_id, statesref.clone() ); } } } else { - let states = &mut *zlock!(statesref); + let mut lock = zlock!(statesref); + let states = &mut *lock; states.global_pending_queries += 1; + drop(lock); let handler = InitialRepliesHandler { statesref: statesref.clone(), From 87acf151b151fd91dd5887402c30920a32d6371e Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 15:23:54 +0100 Subject: [PATCH 82/88] Update key mapping --- zenoh-ext/src/advanced_publisher.rs | 7 +++++-- zenoh-ext/src/advanced_subscriber.rs | 15 +++++++++------ zenoh/src/api/admin.rs | 10 +++++++++- zenoh/src/lib.rs | 6 ++++++ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 0013ad6343..35d0d0246b 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -24,7 +24,7 @@ use zenoh::{ EncodingBuilderTrait, QoSBuilderTrait, SampleBuilderTrait, TimestampBuilderTrait, }, }, - key_expr::KeyExpr, + key_expr::{keyexpr, KeyExpr}, liveliness::LivelinessToken, pubsub::{ PublicationBuilder, PublicationBuilderDelete, PublicationBuilderPut, Publisher, @@ -35,9 +35,12 @@ use zenoh::{ session::EntityGlobalId, Resolvable, Resolve, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_EMPTY, }; +use zenoh_macros::ke; use crate::advanced_cache::{AdvancedCache, AdvancedCacheBuilder, CacheConfig, KE_UHLC}; +pub(crate) static KE_PUB: &keyexpr = ke!("pub"); + #[derive(PartialEq)] #[zenoh_macros::unstable] pub(crate) enum Sequencing { @@ -251,7 +254,7 @@ impl<'a> AdvancedPublisher<'a> { .express(conf.is_express) .wait()?; let id = publisher.id(); - let prefix = KE_ADV_PREFIX / &id.zid().into_keyexpr(); + let prefix = KE_ADV_PREFIX / KE_PUB / &id.zid().into_keyexpr(); let prefix = match conf.sequencing { Sequencing::SequenceNumber => { prefix / &KeyExpr::try_from(id.eid().to_string()).unwrap() diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 9c197d3fa4..2acd65746e 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -24,7 +24,8 @@ use zenoh::{ }, sample::{Locality, Sample, SampleKind}, session::{EntityGlobalId, EntityId}, - Resolvable, Resolve, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_EMPTY, KE_STARSTAR, + Resolvable, Resolve, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_EMPTY, KE_PUB, KE_STAR, + KE_STARSTAR, KE_SUB, }; use zenoh_util::{Timed, TimedEvent, Timer}; #[zenoh_macros::unstable] @@ -510,6 +511,7 @@ impl Timed for PeriodicQuery { if let Some(state) = states.sequenced_states.get_mut(&self.source_id) { state.pending_queries += 1; let query_expr = KE_ADV_PREFIX + / KE_STAR / &self.source_id.zid().into_keyexpr() / &KeyExpr::try_from(self.source_id.eid().to_string()).unwrap() / KE_STARSTAR @@ -607,6 +609,7 @@ impl AdvancedSubscriber { { state.pending_queries += 1; let query_expr = KE_ADV_PREFIX + / KE_STAR / &source_id.zid().into_keyexpr() / &KeyExpr::try_from(source_id.eid().to_string()).unwrap() / KE_STARSTAR @@ -863,7 +866,7 @@ impl AdvancedSubscriber { conf .session .liveliness() - .declare_subscriber(KE_ADV_PREFIX / KE_STARSTAR / KE_AT / &key_expr) + .declare_subscriber(KE_ADV_PREFIX / KE_PUB / KE_STARSTAR / KE_AT / &key_expr) // .declare_subscriber(keformat!(ke_liveliness_all::formatter(), zid = 0, eid = 0, remaining = key_expr).unwrap()) .history(true) .callback(live_callback) @@ -878,6 +881,7 @@ impl AdvancedSubscriber { let token = if conf.liveliness { let prefix = KE_ADV_PREFIX + / KE_SUB / &subscriber.id().zid().into_keyexpr() / &KeyExpr::try_from(subscriber.id().eid().to_string()).unwrap(); let prefix = match meta { @@ -959,10 +963,9 @@ impl AdvancedSubscriber { #[zenoh_macros::unstable] pub fn detect_publishers(&self) -> LivelinessSubscriberBuilder<'_, '_, DefaultHandler> { - self.subscriber - .session() - .liveliness() - .declare_subscriber(KE_ADV_PREFIX / KE_STARSTAR / KE_AT / self.subscriber.key_expr()) + self.subscriber.session().liveliness().declare_subscriber( + KE_ADV_PREFIX / KE_PUB / KE_STARSTAR / KE_AT / self.subscriber.key_expr(), + ) } /// Undeclares this AdvancedSubscriber diff --git a/zenoh/src/api/admin.rs b/zenoh/src/api/admin.rs index 3f6f032009..28808ee664 100644 --- a/zenoh/src/api/admin.rs +++ b/zenoh/src/api/admin.rs @@ -49,10 +49,18 @@ pub static KE_ADV_PREFIX: &keyexpr = ke!("@adv"); #[cfg(not(feature = "internal"))] static KE_ADV_PREFIX: &keyexpr = ke!("@adv"); #[cfg(feature = "internal")] +pub static KE_PUB: &keyexpr = ke!("pub"); +#[cfg(not(feature = "internal"))] +static KE_PUB: &keyexpr = ke!("pub"); +#[cfg(feature = "internal")] +pub static KE_SUB: &keyexpr = ke!("sub"); +#[cfg(feature = "internal")] pub static KE_EMPTY: &keyexpr = ke!("_"); #[cfg(not(feature = "internal"))] static KE_EMPTY: &keyexpr = ke!("_"); #[cfg(feature = "internal")] +pub static KE_STAR: &keyexpr = ke!("*"); +#[cfg(feature = "internal")] pub static KE_STARSTAR: &keyexpr = ke!("**"); #[cfg(not(feature = "internal"))] static KE_STARSTAR: &keyexpr = ke!("**"); @@ -73,7 +81,7 @@ pub(crate) fn init(session: WeakSession) { })), ); - let adv_prefix = KE_ADV_PREFIX / own_zid / KE_EMPTY / KE_EMPTY / KE_AT / KE_AT; + let adv_prefix = KE_ADV_PREFIX / KE_PUB / own_zid / KE_EMPTY / KE_EMPTY / KE_AT / KE_AT; let _admin_adv_qabl = session.declare_queryable_inner( &KeyExpr::from(&adv_prefix / own_zid / KE_SESSION / KE_STARSTAR), diff --git a/zenoh/src/lib.rs b/zenoh/src/lib.rs index 1341f16ac2..119ee34533 100644 --- a/zenoh/src/lib.rs +++ b/zenoh/src/lib.rs @@ -88,7 +88,13 @@ pub use api::admin::KE_AT; #[cfg(feature = "internal")] pub use api::admin::KE_EMPTY; #[cfg(feature = "internal")] +pub use api::admin::KE_PUB; +#[cfg(feature = "internal")] +pub use api::admin::KE_STAR; +#[cfg(feature = "internal")] pub use api::admin::KE_STARSTAR; +#[cfg(feature = "internal")] +pub use api::admin::KE_SUB; lazy_static::lazy_static!( static ref LONG_VERSION: String = format!("{} built with {}", GIT_VERSION, env!("RUSTC_VERSION")); From 633233196f2e249166d8111347e3069cdc2c01e9 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 16:11:10 +0100 Subject: [PATCH 83/88] Improve doc --- zenoh-ext/src/advanced_publisher.rs | 12 ++++++---- zenoh-ext/src/advanced_subscriber.rs | 34 ++++++++++++++++++++++------ zenoh-ext/src/publisher_ext.rs | 28 ++++++++++------------- zenoh-ext/src/subscriber_ext.rs | 14 +++++++----- 4 files changed, 54 insertions(+), 34 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 35d0d0246b..2c0e1cdc83 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -114,16 +114,18 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { } } - /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. + /// Allow matching [`AdvancedSubscribers`](crate::AdvancedSubscriber) to detect lost samples and optionally ask for retransimission. /// - /// Retransmission can only be achieved if history is enabled. + /// Retransmission can only be achieved if [`cache`](crate::AdvancedPublisherBuilder::cache) is enabled. #[zenoh_macros::unstable] pub fn sample_miss_detection(mut self) -> Self { self.sequencing = Sequencing::SequenceNumber; self } - /// Change the history size for each resource. + /// Attach a cache to this [`AdvancedPublisher`]. + /// + /// The cache can be used for history and/or recovery. #[zenoh_macros::unstable] pub fn cache(mut self, config: CacheConfig) -> Self { self.cache = true; @@ -134,9 +136,9 @@ impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { self } - /// Allow this publisher to be detected by subscribers. + /// Allow this [`AdvancedPublisher`] to be detected by [`AdvancedSubscribers`](crate::AdvancedSubscriber). /// - /// This allows Subscribers to retrieve the local history. + /// This allows [`AdvancedSubscribers`](crate::AdvancedSubscriber) to retrieve the local history. #[zenoh_macros::unstable] pub fn publisher_detection(mut self) -> Self { self.liveliness = true; diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 2acd65746e..2490108c25 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -61,8 +61,8 @@ pub struct HistoryConfig { impl HistoryConfig { /// Enable detection of late joiner publishers and query for their historical data. /// - /// Let joiner detection can only be achieved for Publishers that enable publisher_detection. - /// History can only be retransmitted by Publishers that enable caching. + /// Let joiner detection can only be achieved for [`AdvancedPublishers`](crate::AdvancedPublisher) that enable publisher_detection. + /// History can only be retransmitted by [`AdvancedPublishers`](crate::AdvancedPublisher) that enable [`cache`](crate::AdvancedPublisherBuilder::cache). #[inline] #[zenoh_macros::unstable] pub fn detect_late_publishers(mut self) -> Self { @@ -107,7 +107,9 @@ impl RecoveryConfig { /// This allows to retrieve the last Sample(s) if the last Sample(s) is/are lost. /// So it is useful for sporadic publications but useless for periodic publications /// with a period smaller or equal to this period. - /// Retransmission can only be achieved by Publishers that also activate retransmission. + /// Retransmission can only be achieved by [`AdvancedPublishers`](crate::AdvancedPublisher) + /// that enable [`cache`](crate::AdvancedPublisherBuilder::cache) and + /// [`sample_miss_detection`](crate::AdvancedPublisherBuilder::sample_miss_detection). #[zenoh_macros::unstable] #[inline] pub fn periodic_queries(mut self, period: Option) -> Self { @@ -227,8 +229,9 @@ impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { /// Ask for retransmission of detected lost Samples. /// - /// Retransmission can only be achieved by Publishers that enable - /// caching and sample_miss_detection. + /// Retransmission can only be achieved by [`AdvancedPublishers`](crate::AdvancedPublisher) + /// that enable [`cache`](crate::AdvancedPublisherBuilder::cache) and + /// [`sample_miss_detection`](crate::AdvancedPublisherBuilder::sample_miss_detection). #[zenoh_macros::unstable] #[inline] pub fn recovery(mut self, conf: RecoveryConfig) -> Self { @@ -254,7 +257,7 @@ impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { /// Enable query for historical data. /// - /// History can only be retransmitted by Publishers that enable caching. + /// History can only be retransmitted by [`AdvancedPublishers`](crate::AdvancedPublisher) that enable [`cache`](crate::AdvancedPublisherBuilder::cache). #[zenoh_macros::unstable] #[inline] pub fn history(mut self, config: HistoryConfig) -> Self { @@ -953,6 +956,10 @@ impl AdvancedSubscriber { &mut self.receiver } + /// Declares a listener to detect missed samples. + /// + /// Missed samples can only be detected from [`AdvancedPublisher`](crate::AdvancedPublisher) that + /// enable [`sample_miss_detection`](crate::AdvancedPublisherBuilder::sample_miss_detection). #[zenoh_macros::unstable] pub fn sample_miss_listener(&self) -> SampleMissListenerBuilder<'_, DefaultHandler> { SampleMissListenerBuilder { @@ -961,6 +968,10 @@ impl AdvancedSubscriber { } } + /// Declares a listener to detect matching publishers. + /// + /// Only [`AdvancedPublisher`](crate::AdvancedPublisher) that enable + /// [`publisher_detection`](crate::AdvancedPublisherBuilder::publisher_detection) can be detected. #[zenoh_macros::unstable] pub fn detect_publishers(&self) -> LivelinessSubscriberBuilder<'_, '_, DefaultHandler> { self.subscriber.session().liveliness().declare_subscriber( @@ -1109,6 +1120,7 @@ impl Drop for TimestampedRepliesHandler { } } +/// A struct that represent missed samples. #[zenoh_macros::unstable] pub struct Miss { source: EntityGlobalId, @@ -1116,14 +1128,21 @@ pub struct Miss { } impl Miss { + /// The source of missed samples. pub fn source(&self) -> EntityGlobalId { self.source } + + /// The number of missed samples. pub fn nb(&self) -> u32 { self.nb } } +/// A listener to detect missed samples. +/// +/// Missed samples can only be detected from [`AdvancedPublisher`](crate::AdvancedPublisher) that +/// enable [`sample_miss_detection`](crate::AdvancedPublisherBuilder::sample_miss_detection). #[zenoh_macros::unstable] pub struct SampleMissListener { id: usize, @@ -1182,6 +1201,7 @@ impl std::ops::DerefMut for SampleMissListener { } } +/// A [`Resolvable`] returned when undeclaring a [`SampleMissListener`]. #[zenoh_macros::unstable] pub struct SampleMissHandlerUndeclaration(SampleMissListener); @@ -1207,7 +1227,7 @@ impl IntoFuture for SampleMissHandlerUndeclaration { } } -/// A builder for initializing a [`SampleMissHandler`]. +/// A builder for initializing a [`SampleMissListener`]. #[zenoh_macros::unstable] pub struct SampleMissListenerBuilder<'a, Handler, const BACKGROUND: bool = false> { statesref: &'a Arc>, diff --git a/zenoh-ext/src/publisher_ext.rs b/zenoh-ext/src/publisher_ext.rs index 2fa5878404..de045d0ff0 100644 --- a/zenoh-ext/src/publisher_ext.rs +++ b/zenoh-ext/src/publisher_ext.rs @@ -18,22 +18,20 @@ use crate::{advanced_cache::CacheConfig, AdvancedPublisherBuilder}; /// Some extensions to the [`zenoh::publication::PublisherBuilder`](zenoh::publication::PublisherBuilder) #[zenoh_macros::unstable] pub trait AdvancedPublisherBuilderExt<'a, 'b, 'c> { - /// Allow matching Subscribers to detect lost samples and - /// optionally ask for retransimission. - /// - /// Retransmission can only be achieved if history is enabled. + /// Allow matching [`AdvancedSubscribers`](crate::AdvancedSubscriber) to recover history and/or missed samples. #[zenoh_macros::unstable] fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c>; - /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. + /// Allow matching [`AdvancedSubscribers`](crate::AdvancedSubscriber) to detect lost samples + /// and optionally ask for retransimission. /// - /// Retransmission can only be achieved if cache is enabled. + /// Retransmission can only be achieved if [`cache`](crate::AdvancedPublisherBuilder::cache) is also enabled. #[zenoh_macros::unstable] fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; - /// Allow this publisher to be detected by subscribers. + /// Allow this publisher to be detected by [`AdvancedSubscribers`](crate::AdvancedSubscriber). /// - /// This allows Subscribers to retrieve the local history. + /// This allows [`AdvancedSubscribers`](crate::AdvancedSubscriber) to retrieve the local history. #[zenoh_macros::unstable] fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c>; @@ -44,26 +42,24 @@ pub trait AdvancedPublisherBuilderExt<'a, 'b, 'c> { #[zenoh_macros::unstable] impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { - /// Allow matching Subscribers to detect lost samples and - /// optionally ask for retransimission. - /// - /// Retransmission can only be achieved if history is enabled. + /// Allow matching [`AdvancedSubscribers`](crate::AdvancedSubscriber) to recover history and/or missed samples. #[zenoh_macros::unstable] fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self).cache(config) } - /// Allow matching Subscribers to detect lost samples and optionally ask for retransimission. + /// Allow matching [`AdvancedSubscribers`](crate::AdvancedSubscriber) to detect lost samples + /// and optionally ask for retransimission. /// - /// Retransmission can only be achieved if cache is enabled. + /// Retransmission can only be achieved if [`cache`](crate::AdvancedPublisherBuilder::cache) is also enabled. #[zenoh_macros::unstable] fn sample_miss_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self).sample_miss_detection() } - /// Allow this publisher to be detected by subscribers. + /// Allow this publisher to be detected by [`AdvancedSubscribers`](crate::AdvancedSubscriber). /// - /// This allows Subscribers to retrieve the local history. + /// This allows [`AdvancedSubscribers`](crate::AdvancedSubscriber) to retrieve the local history. #[zenoh_macros::unstable] fn publisher_detection(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { AdvancedPublisherBuilder::new(self).publisher_detection() diff --git a/zenoh-ext/src/subscriber_ext.rs b/zenoh-ext/src/subscriber_ext.rs index badd9b36bc..4441ebabdc 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -137,14 +137,15 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { pub trait AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> { /// Enable query for historical data. /// - /// History can only be retransmitted by Publishers that enable caching. + /// History can only be retransmitted by [`AdvancedPublishers`](crate::AdvancedPublisher) that enable [`cache`](crate::AdvancedPublisherBuilder::cache). #[zenoh_macros::unstable] fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; /// Ask for retransmission of detected lost Samples. /// - /// Retransmission can only be achieved by Publishers that enable - /// caching and sample_miss_detection. + /// Retransmission can only be achieved by [`AdvancedPublishers`](crate::AdvancedPublisher) + /// that enable [`cache`](crate::AdvancedPublisherBuilder::cache) and + /// [`sample_miss_detection`](crate::AdvancedPublisherBuilder::sample_miss_detection). #[zenoh_macros::unstable] fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler>; @@ -271,7 +272,7 @@ impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> { /// Enable query for historical data. /// - /// History can only be retransmitted by Publishers that enable caching. + /// History can only be retransmitted by [`AdvancedPublishers`](crate::AdvancedPublisher) that enable [`cache`](crate::AdvancedPublisherBuilder::cache). #[zenoh_macros::unstable] fn history(self, config: HistoryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self).history(config) @@ -279,8 +280,9 @@ impl<'a, 'b, 'c, Handler> AdvancedSubscriberBuilderExt<'a, 'b, 'c, Handler> /// Ask for retransmission of detected lost Samples. /// - /// Retransmission can only be achieved by Publishers that enable - /// caching and sample_miss_detection. + /// Retransmission can only be achieved by [`AdvancedPublishers`](crate::AdvancedPublisher) + /// that enable [`cache`](crate::AdvancedPublisherBuilder::cache) and + /// [`sample_miss_detection`](crate::AdvancedPublisherBuilder::sample_miss_detection). #[zenoh_macros::unstable] fn recovery(self, conf: RecoveryConfig) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { AdvancedSubscriberBuilder::new(self).recovery(conf) From b807ee8c3a3622b17e8070c468539ad86a221315 Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Tue, 10 Dec 2024 17:00:56 +0100 Subject: [PATCH 84/88] fix: fix callback API (#1647) --- zenoh-ext/src/advanced_subscriber.rs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 2490108c25..e1e3976a82 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -157,25 +157,11 @@ impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { /// Add callback to AdvancedSubscriber. #[inline] #[zenoh_macros::unstable] - pub fn callback( - self, - callback: Callback, - ) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback> + pub fn callback(self, callback: F) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback> where - Callback: Fn(Sample) + Send + Sync + 'static, + F: Fn(Sample) + Send + Sync + 'static, { - AdvancedSubscriberBuilder { - session: self.session, - key_expr: self.key_expr.map(|s| s.into_owned()), - origin: self.origin, - retransmission: self.retransmission, - query_target: self.query_target, - query_timeout: self.query_timeout, - history: self.history, - liveliness: self.liveliness, - meta_key_expr: self.meta_key_expr, - handler: callback, - } + self.with(Callback::new(Arc::new(callback))) } /// Add callback to `AdvancedSubscriber`. @@ -184,12 +170,12 @@ impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { /// If your callback is also accepted by the [`callback`](AdvancedSubscriberBuilder::callback) method, we suggest you use it instead of `callback_mut` #[inline] #[zenoh_macros::unstable] - pub fn callback_mut( + pub fn callback_mut( self, - callback: CallbackMut, - ) -> AdvancedSubscriberBuilder<'a, 'b, 'c, impl Fn(Sample) + Send + Sync + 'static> + callback: F, + ) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback> where - CallbackMut: FnMut(Sample) + Send + Sync + 'static, + F: FnMut(Sample) + Send + Sync + 'static, { self.callback(locked(callback)) } From 20960852fc8d1c7d01d88156624815dcb3360935 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 17:10:24 +0100 Subject: [PATCH 85/88] Update doc --- zenoh-ext/src/advanced_publisher.rs | 29 +++++++++++----------------- zenoh-ext/src/advanced_subscriber.rs | 15 -------------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index 2c0e1cdc83..e99a87d1a8 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -315,24 +315,12 @@ impl<'a> AdvancedPublisher<'a> { } /// Returns the [`EntityGlobalId`] of this Publisher. - /// - /// # Examples - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// - /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); - /// let publisher = session.declare_publisher("key/expression") - /// .await - /// .unwrap(); - /// let publisher_id = publisher.id(); - /// # } - /// ``` #[zenoh_macros::unstable] pub fn id(&self) -> EntityGlobalId { self.publisher.id() } + /// Returns the [`KeyExpr`] of this Publisher. #[inline] #[zenoh_macros::unstable] pub fn key_expr(&self) -> &KeyExpr<'a> { @@ -366,9 +354,10 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { + /// use zenoh_ext::AdvancedSubscriberBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); - /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); /// publisher.put("value").await.unwrap(); /// # } /// ``` @@ -400,9 +389,10 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { + /// use zenoh_ext::AdvancedSubscriberBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); - /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); /// publisher.delete().await.unwrap(); /// # } /// ``` @@ -433,9 +423,10 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { + /// use zenoh_ext::AdvancedSubscriberBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); - /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); /// let matching_subscribers: bool = publisher /// .matching_status() /// .await @@ -457,9 +448,10 @@ impl<'a> AdvancedPublisher<'a> { /// ```no_run /// # #[tokio::main] /// # async fn main() { + /// use zenoh_ext::AdvancedSubscriberBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); - /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); /// let matching_listener = publisher.matching_listener().await.unwrap(); /// while let Ok(matching_status) = matching_listener.recv_async().await { /// if matching_status.matching() { @@ -483,9 +475,10 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { + /// use zenoh_ext::AdvancedSubscriberBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); - /// let publisher = session.declare_publisher("key/expression").await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); /// publisher.undeclare().await.unwrap(); /// # } /// ``` diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index e1e3976a82..97bb7c5197 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -900,21 +900,6 @@ impl AdvancedSubscriber { } /// Returns the [`EntityGlobalId`] of this AdvancedSubscriber. - /// - /// # Examples - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use zenoh_ext::AdvancedSubscriberBuilderExt; - /// - /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); - /// let subscriber = session.declare_subscriber("key/expression") - /// .advanced() - /// .await - /// .unwrap(); - /// let subscriber_id = subscriber.id(); - /// # } - /// ``` #[zenoh_macros::unstable] pub fn id(&self) -> EntityGlobalId { self.subscriber.id() From dce33042be1fcb98164e14bff069f559db6b7690 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 17:13:27 +0100 Subject: [PATCH 86/88] Fix ke_liveliness --- zenoh-ext/src/advanced_cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs index b3d6d4b661..c18add5a59 100644 --- a/zenoh-ext/src/advanced_cache.rs +++ b/zenoh-ext/src/advanced_cache.rs @@ -34,7 +34,7 @@ use zenoh::{ pub(crate) static KE_UHLC: &keyexpr = ke!("uhlc"); #[zenoh_macros::unstable] kedefine!( - pub(crate) ke_liveliness: "@adv/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", + pub(crate) ke_liveliness: "@adv/${entity:*}/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", ); #[zenoh_macros::unstable] From e547a237348a31cfe862ef2b9baf1536132469dc Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Tue, 10 Dec 2024 17:45:09 +0100 Subject: [PATCH 87/88] Fix doc --- zenoh-ext/src/advanced_publisher.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zenoh-ext/src/advanced_publisher.rs b/zenoh-ext/src/advanced_publisher.rs index e99a87d1a8..023e509d57 100644 --- a/zenoh-ext/src/advanced_publisher.rs +++ b/zenoh-ext/src/advanced_publisher.rs @@ -354,7 +354,7 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh_ext::AdvancedSubscriberBuilderExt; + /// use zenoh_ext::AdvancedPublisherBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); @@ -389,7 +389,7 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh_ext::AdvancedSubscriberBuilderExt; + /// use zenoh_ext::AdvancedPublisherBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); @@ -423,7 +423,7 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh_ext::AdvancedSubscriberBuilderExt; + /// use zenoh_ext::AdvancedPublisherBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); @@ -448,7 +448,7 @@ impl<'a> AdvancedPublisher<'a> { /// ```no_run /// # #[tokio::main] /// # async fn main() { - /// use zenoh_ext::AdvancedSubscriberBuilderExt; + /// use zenoh_ext::AdvancedPublisherBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); @@ -475,7 +475,7 @@ impl<'a> AdvancedPublisher<'a> { /// ``` /// # #[tokio::main] /// # async fn main() { - /// use zenoh_ext::AdvancedSubscriberBuilderExt; + /// use zenoh_ext::AdvancedPublisherBuilderExt; /// /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); From e870d799075cc14718d879db2e401f91e59b6772 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 11 Dec 2024 09:31:21 +0100 Subject: [PATCH 88/88] Fix doc --- zenoh-ext/src/advanced_subscriber.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index 97bb7c5197..e0d9ed7e3a 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -61,7 +61,7 @@ pub struct HistoryConfig { impl HistoryConfig { /// Enable detection of late joiner publishers and query for their historical data. /// - /// Let joiner detection can only be achieved for [`AdvancedPublishers`](crate::AdvancedPublisher) that enable publisher_detection. + /// Late joiner detection can only be achieved for [`AdvancedPublishers`](crate::AdvancedPublisher) that enable publisher_detection. /// History can only be retransmitted by [`AdvancedPublishers`](crate::AdvancedPublisher) that enable [`cache`](crate::AdvancedPublisherBuilder::cache). #[inline] #[zenoh_macros::unstable]