From b7d20515c923a673e0e4977651098daa5abe1984 Mon Sep 17 00:00:00 2001 From: Oussama Teffahi <70609372+oteffahi@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:04:09 +0100 Subject: [PATCH 01/23] Add TCP buffer sizes configuration (#1629) * Move socket to stream/listener functions to zenoh-link-commons * Use TcpSocketUtils to create listener/stream in TLS link * Change TcpSocketUtils to TcpSocketConfig * Add TCP buffer size endpoint parameters and parsing * Add TCP buffer sizes to config file * Rename endpoint parameters to match config file * Correct typo * Fix more typos * Remove unused cargo dependency * Add TCP buffer sizes config tests * Allow zero-buffer tests to panic only on mac * Remove test headers from function --- Cargo.lock | 2 +- DEFAULT_CONFIG.json5 | 6 + commons/zenoh-config/src/lib.rs | 4 + io/zenoh-link-commons/src/lib.rs | 3 + io/zenoh-link-commons/src/tcp.rs | 100 +++++++++++++++++ io/zenoh-link/src/lib.rs | 13 ++- io/zenoh-links/zenoh-link-tcp/Cargo.toml | 2 +- io/zenoh-links/zenoh-link-tcp/src/lib.rs | 2 + io/zenoh-links/zenoh-link-tcp/src/unicast.rs | 92 +++------------ io/zenoh-links/zenoh-link-tcp/src/utils.rs | 80 +++++++++++++ io/zenoh-links/zenoh-link-tls/src/unicast.rs | 42 +++---- io/zenoh-links/zenoh-link-tls/src/utils.rs | 67 +++++++++-- zenoh/tests/tcp_buffers.rs | 111 +++++++++++++++++++ 13 files changed, 412 insertions(+), 112 deletions(-) create mode 100644 io/zenoh-link-commons/src/tcp.rs create mode 100644 io/zenoh-links/zenoh-link-tcp/src/utils.rs create mode 100644 zenoh/tests/tcp_buffers.rs diff --git a/Cargo.lock b/Cargo.lock index 0cb5efd249..e47d08dd0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5336,11 +5336,11 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "zenoh-config", "zenoh-core", "zenoh-link-commons", "zenoh-protocol", "zenoh-result", - "zenoh-util", ] [[package]] diff --git a/DEFAULT_CONFIG.json5 b/DEFAULT_CONFIG.json5 index 6422759952..361b8de7fa 100644 --- a/DEFAULT_CONFIG.json5 +++ b/DEFAULT_CONFIG.json5 @@ -495,6 +495,12 @@ // note that mTLS (client authentication) is required for a listener to disconnect a client on expiration close_link_on_expiration: false, }, + /// Optional configuration for TCP system buffers sizes. Applies to TCP and TLS links. + /// + /// Configure TCP read buffer size (bytes) + // tcp_rx_buffer: 123456, + /// Configure TCP write buffer size (bytes) + // tcp_tx_buffer: 123456, }, /// Shared memory configuration. /// NOTE: shared memory can be used only if zenoh is compiled with "shared-memory" feature, otherwise diff --git a/commons/zenoh-config/src/lib.rs b/commons/zenoh-config/src/lib.rs index 87c1b9b90d..f0ba50d0a6 100644 --- a/commons/zenoh-config/src/lib.rs +++ b/commons/zenoh-config/src/lib.rs @@ -515,6 +515,10 @@ validated_struct::validator! { UnixPipeConf { file_access_mask: Option }, + /// Configure TCP read buffer size + pub tcp_rx_buffer: Option, + /// Configure TCP write buffer size + pub tcp_tx_buffer: Option, }, pub shared_memory: ShmConf { diff --git a/io/zenoh-link-commons/src/lib.rs b/io/zenoh-link-commons/src/lib.rs index fae26cd02d..ed6f5e72f8 100644 --- a/io/zenoh-link-commons/src/lib.rs +++ b/io/zenoh-link-commons/src/lib.rs @@ -21,6 +21,7 @@ extern crate alloc; mod listener; mod multicast; +pub mod tcp; #[cfg(feature = "tls")] pub mod tls; mod unicast; @@ -44,6 +45,8 @@ use zenoh_result::ZResult; /*************************************/ pub const BIND_INTERFACE: &str = "iface"; +pub const TCP_TX_BUFFER_SIZE: &str = "tcp_tx_buffer"; +pub const TCP_RX_BUFFER_SIZE: &str = "tcp_rx_buffer"; #[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)] pub struct Link { diff --git a/io/zenoh-link-commons/src/tcp.rs b/io/zenoh-link-commons/src/tcp.rs new file mode 100644 index 0000000000..db7da4d562 --- /dev/null +++ b/io/zenoh-link-commons/src/tcp.rs @@ -0,0 +1,100 @@ +// +// 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 std::net::SocketAddr; + +use tokio::net::{TcpListener, TcpSocket, TcpStream}; +use zenoh_result::{zerror, ZResult}; + +pub struct TcpSocketConfig<'a> { + tx_buffer_size: Option, + rx_buffer_size: Option, + iface: Option<&'a str>, +} + +impl<'a> TcpSocketConfig<'a> { + pub fn new( + tx_buffer_size: Option, + rx_buffer_size: Option, + iface: Option<&'a str>, + ) -> Self { + Self { + tx_buffer_size, + rx_buffer_size, + iface, + } + } + + /// Build a new TCPListener bound to `addr` with the given configuration parameters + pub fn new_listener(&self, addr: &SocketAddr) -> ZResult<(TcpListener, SocketAddr)> { + let socket = self.socket_with_config(addr)?; + // Build a TcpListener from TcpSocket + // https://docs.rs/tokio/latest/tokio/net/struct.TcpSocket.html + socket.set_reuseaddr(true)?; + socket.bind(*addr).map_err(|e| zerror!("{}: {}", addr, e))?; + // backlog (the maximum number of pending connections are queued): 1024 + let listener = socket + .listen(1024) + .map_err(|e| zerror!("{}: {}", addr, e))?; + + let local_addr = listener + .local_addr() + .map_err(|e| zerror!("{}: {}", addr, e))?; + + Ok((listener, local_addr)) + } + + /// Connect to a TCP socket address at `dst_addr` with the given configuration parameters + pub async fn new_link( + &self, + dst_addr: &SocketAddr, + ) -> ZResult<(TcpStream, SocketAddr, SocketAddr)> { + let socket = self.socket_with_config(dst_addr)?; + // Build a TcpStream from TcpSocket + // https://docs.rs/tokio/latest/tokio/net/struct.TcpSocket.html + let stream = socket + .connect(*dst_addr) + .await + .map_err(|e| zerror!("{}: {}", dst_addr, e))?; + + let src_addr = stream + .local_addr() + .map_err(|e| zerror!("{}: {}", dst_addr, e))?; + + let dst_addr = stream + .peer_addr() + .map_err(|e| zerror!("{}: {}", dst_addr, e))?; + + Ok((stream, src_addr, dst_addr)) + } + + /// Creates a TcpSocket with the provided config + fn socket_with_config(&self, addr: &SocketAddr) -> ZResult { + let socket = match addr { + SocketAddr::V4(_) => TcpSocket::new_v4(), + SocketAddr::V6(_) => TcpSocket::new_v6(), + }?; + + if let Some(iface) = self.iface { + zenoh_util::net::set_bind_to_device_tcp_socket(&socket, iface)?; + } + if let Some(size) = self.tx_buffer_size { + socket.set_send_buffer_size(size)?; + } + if let Some(size) = self.rx_buffer_size { + socket.set_recv_buffer_size(size)?; + } + + Ok(socket) + } +} diff --git a/io/zenoh-link/src/lib.rs b/io/zenoh-link/src/lib.rs index b092aaf9d6..d621182b5b 100644 --- a/io/zenoh-link/src/lib.rs +++ b/io/zenoh-link/src/lib.rs @@ -34,7 +34,9 @@ use zenoh_link_serial::{LinkManagerUnicastSerial, SerialLocatorInspector, SERIAL #[cfg(feature = "transport_tcp")] pub use zenoh_link_tcp as tcp; #[cfg(feature = "transport_tcp")] -use zenoh_link_tcp::{LinkManagerUnicastTcp, TcpLocatorInspector, TCP_LOCATOR_PREFIX}; +use zenoh_link_tcp::{ + LinkManagerUnicastTcp, TcpConfigurator, TcpLocatorInspector, TCP_LOCATOR_PREFIX, +}; #[cfg(feature = "transport_tls")] pub use zenoh_link_tls as tls; #[cfg(feature = "transport_tls")] @@ -172,6 +174,8 @@ impl LocatorInspector { } #[derive(Default)] pub struct LinkConfigurator { + #[cfg(feature = "transport_tcp")] + tcp_inspector: TcpConfigurator, #[cfg(feature = "transport_quic")] quic_inspector: QuicConfigurator, #[cfg(feature = "transport_tls")] @@ -199,6 +203,13 @@ impl LinkConfigurator { errors.insert(proto, e); } }; + #[cfg(feature = "transport_tcp")] + { + insert_config( + TCP_LOCATOR_PREFIX.into(), + self.tcp_inspector.inspect_config(config), + ); + } #[cfg(feature = "transport_quic")] { insert_config( diff --git a/io/zenoh-links/zenoh-link-tcp/Cargo.toml b/io/zenoh-links/zenoh-link-tcp/Cargo.toml index 8a631bdfbc..c6bce930ea 100644 --- a/io/zenoh-links/zenoh-link-tcp/Cargo.toml +++ b/io/zenoh-links/zenoh-link-tcp/Cargo.toml @@ -30,8 +30,8 @@ socket2 = { workspace = true } tokio = { workspace = true, features = ["net", "io-util", "rt", "time"] } tokio-util = { workspace = true, features = ["rt"] } tracing = {workspace = true} +zenoh-config = { workspace = true } zenoh-core = { workspace = true } zenoh-link-commons = { workspace = true } zenoh-protocol = { workspace = true } zenoh-result = { workspace = true } -zenoh-util = { workspace = true } diff --git a/io/zenoh-links/zenoh-link-tcp/src/lib.rs b/io/zenoh-links/zenoh-link-tcp/src/lib.rs index 0654943f4f..bd642ece83 100644 --- a/io/zenoh-links/zenoh-link-tcp/src/lib.rs +++ b/io/zenoh-links/zenoh-link-tcp/src/lib.rs @@ -29,7 +29,9 @@ use zenoh_protocol::{ use zenoh_result::{zerror, ZResult}; mod unicast; +mod utils; pub use unicast::*; +pub use utils::TcpConfigurator; // Default MTU (TCP PDU) in bytes. // NOTE: Since TCP is a byte-stream oriented transport, theoretically it has diff --git a/io/zenoh-links/zenoh-link-tcp/src/unicast.rs b/io/zenoh-links/zenoh-link-tcp/src/unicast.rs index e3eb9d9796..d61bf515dd 100644 --- a/io/zenoh-links/zenoh-link-tcp/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-tcp/src/unicast.rs @@ -16,12 +16,12 @@ use std::{cell::UnsafeCell, convert::TryInto, fmt, net::SocketAddr, sync::Arc, t use async_trait::async_trait; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, - net::{TcpListener, TcpSocket, TcpStream}, + net::{TcpListener, TcpStream}, }; use tokio_util::sync::CancellationToken; use zenoh_link_commons::{ - get_ip_interface_names, LinkAuthId, LinkManagerUnicastTrait, LinkUnicast, LinkUnicastTrait, - ListenersUnicastIP, NewLinkChannelSender, BIND_INTERFACE, + get_ip_interface_names, tcp::TcpSocketConfig, LinkAuthId, LinkManagerUnicastTrait, LinkUnicast, + LinkUnicastTrait, ListenersUnicastIP, NewLinkChannelSender, }; use zenoh_protocol::{ core::{EndPoint, Locator}, @@ -29,9 +29,9 @@ use zenoh_protocol::{ }; use zenoh_result::{bail, zerror, Error as ZError, ZResult}; -use super::{ - get_tcp_addrs, TCP_ACCEPT_THROTTLE_TIME, TCP_DEFAULT_MTU, TCP_LINGER_TIMEOUT, - TCP_LOCATOR_PREFIX, +use crate::{ + get_tcp_addrs, utils::TcpLinkConfig, TCP_ACCEPT_THROTTLE_TIME, TCP_DEFAULT_MTU, + TCP_LINGER_TIMEOUT, TCP_LOCATOR_PREFIX, }; pub struct LinkUnicastTcp { @@ -241,80 +241,22 @@ impl LinkManagerUnicastTcp { } } -impl LinkManagerUnicastTcp { - async fn new_link_inner( - &self, - dst_addr: &SocketAddr, - iface: Option<&str>, - ) -> ZResult<(TcpStream, SocketAddr, SocketAddr)> { - let socket = match dst_addr { - SocketAddr::V4(_) => TcpSocket::new_v4(), - SocketAddr::V6(_) => TcpSocket::new_v6(), - }?; - - if let Some(iface) = iface { - zenoh_util::net::set_bind_to_device_tcp_socket(&socket, iface)?; - } - - // Build a TcpStream from TcpSocket - // https://docs.rs/tokio/latest/tokio/net/struct.TcpSocket.html - let stream = socket - .connect(*dst_addr) - .await - .map_err(|e| zerror!("{}: {}", dst_addr, e))?; - - let src_addr = stream - .local_addr() - .map_err(|e| zerror!("{}: {}", dst_addr, e))?; - - let dst_addr = stream - .peer_addr() - .map_err(|e| zerror!("{}: {}", dst_addr, e))?; - - Ok((stream, src_addr, dst_addr)) - } - - async fn new_listener_inner( - &self, - addr: &SocketAddr, - iface: Option<&str>, - ) -> ZResult<(TcpListener, SocketAddr)> { - let socket = match addr { - SocketAddr::V4(_) => TcpSocket::new_v4(), - SocketAddr::V6(_) => TcpSocket::new_v6(), - }?; - - if let Some(iface) = iface { - zenoh_util::net::set_bind_to_device_tcp_socket(&socket, iface)?; - } - - // Build a TcpListener from TcpSocket - // https://docs.rs/tokio/latest/tokio/net/struct.TcpSocket.html - socket.set_reuseaddr(true)?; - socket.bind(*addr).map_err(|e| zerror!("{}: {}", addr, e))?; - // backlog (the maximum number of pending connections are queued): 1024 - let listener = socket - .listen(1024) - .map_err(|e| zerror!("{}: {}", addr, e))?; - - let local_addr = listener - .local_addr() - .map_err(|e| zerror!("{}: {}", addr, e))?; - - Ok((listener, local_addr)) - } -} - #[async_trait] impl LinkManagerUnicastTrait for LinkManagerUnicastTcp { async fn new_link(&self, endpoint: EndPoint) -> ZResult { let dst_addrs = get_tcp_addrs(endpoint.address()).await?; let config = endpoint.config(); - let iface = config.get(BIND_INTERFACE); + + let link_config = TcpLinkConfig::new(&config)?; + let socket_config = TcpSocketConfig::new( + link_config.tx_buffer_size, + link_config.rx_buffer_size, + link_config.bind_iface, + ); let mut errs: Vec = vec![]; for da in dst_addrs { - match self.new_link_inner(&da, iface).await { + match socket_config.new_link(&da).await { Ok((stream, src_addr, dst_addr)) => { let link = Arc::new(LinkUnicastTcp::new(stream, src_addr, dst_addr)); return Ok(LinkUnicast(link)); @@ -339,11 +281,13 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastTcp { async fn new_listener(&self, mut endpoint: EndPoint) -> ZResult { let addrs = get_tcp_addrs(endpoint.address()).await?; let config = endpoint.config(); - let iface = config.get(BIND_INTERFACE); + + let link_config = TcpLinkConfig::new(&config)?; + let socket_config: TcpSocketConfig<'_> = link_config.into(); let mut errs: Vec = vec![]; for da in addrs { - match self.new_listener_inner(&da, iface).await { + match socket_config.new_listener(&da) { Ok((socket, local_addr)) => { // Update the endpoint locator address endpoint = EndPoint::new( diff --git a/io/zenoh-links/zenoh-link-tcp/src/utils.rs b/io/zenoh-links/zenoh-link-tcp/src/utils.rs new file mode 100644 index 0000000000..4969693193 --- /dev/null +++ b/io/zenoh-links/zenoh-link-tcp/src/utils.rs @@ -0,0 +1,80 @@ +// +// 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_config::Config as ZenohConfig; +use zenoh_link_commons::{ + tcp::TcpSocketConfig, ConfigurationInspector, BIND_INTERFACE, TCP_RX_BUFFER_SIZE, + TCP_TX_BUFFER_SIZE, +}; +use zenoh_protocol::core::{parameters, Config}; +use zenoh_result::{zerror, ZResult}; + +#[derive(Default, Clone, Copy, Debug)] +pub struct TcpConfigurator; + +impl ConfigurationInspector for TcpConfigurator { + fn inspect_config(&self, config: &ZenohConfig) -> ZResult { + let mut ps: Vec<(&str, &str)> = vec![]; + let c = config.transport().link(); + + let rx_buffer_size; + if let Some(size) = c.tcp_rx_buffer { + rx_buffer_size = size.to_string(); + ps.push((TCP_RX_BUFFER_SIZE, &rx_buffer_size)); + } + let tx_buffer_size; + if let Some(size) = c.tcp_tx_buffer { + tx_buffer_size = size.to_string(); + ps.push((TCP_TX_BUFFER_SIZE, &tx_buffer_size)); + } + + Ok(parameters::from_iter(ps.drain(..))) + } +} + +pub(crate) struct TcpLinkConfig<'a> { + pub(crate) rx_buffer_size: Option, + pub(crate) tx_buffer_size: Option, + pub(crate) bind_iface: Option<&'a str>, +} + +impl<'a> TcpLinkConfig<'a> { + pub(crate) fn new(config: &'a Config) -> ZResult { + let mut tcp_config = Self { + rx_buffer_size: None, + tx_buffer_size: None, + bind_iface: config.get(BIND_INTERFACE), + }; + + if let Some(size) = config.get(TCP_RX_BUFFER_SIZE) { + tcp_config.rx_buffer_size = Some( + size.parse() + .map_err(|_| zerror!("Unknown TCP read buffer size argument: {}", size))?, + ); + }; + if let Some(size) = config.get(TCP_TX_BUFFER_SIZE) { + tcp_config.tx_buffer_size = Some( + size.parse() + .map_err(|_| zerror!("Unknown TCP write buffer size argument: {}", size))?, + ); + }; + + Ok(tcp_config) + } +} + +impl<'a> From> for TcpSocketConfig<'a> { + fn from(value: TcpLinkConfig<'a>) -> Self { + Self::new(value.tx_buffer_size, value.rx_buffer_size, value.bind_iface) + } +} diff --git a/io/zenoh-links/zenoh-link-tls/src/unicast.rs b/io/zenoh-links/zenoh-link-tls/src/unicast.rs index 046288800e..62250d354a 100644 --- a/io/zenoh-links/zenoh-link-tls/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-tls/src/unicast.rs @@ -324,29 +324,17 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastTls { let connector = TlsConnector::from(config); // Initialize the TcpStream - let tcp_stream = TcpStream::connect(addr).await.map_err(|e| { - zerror!( - "Can not create a new TLS link bound to {:?}: {}", - server_name, - e - ) - })?; - - let src_addr = tcp_stream.local_addr().map_err(|e| { - zerror!( - "Can not create a new TLS link bound to {:?}: {}", - server_name, - e - ) - })?; - - let dst_addr = tcp_stream.peer_addr().map_err(|e| { - zerror!( - "Can not create a new TLS link bound to {:?}: {}", - server_name, - e - ) - })?; + let (tcp_stream, src_addr, dst_addr) = client_config + .tcp_socket_config + .new_link(&addr) + .await + .map_err(|e| { + zerror!( + "Can not create a new TLS link bound to {:?}: {}", + server_name, + e + ) + })?; // Initialize the TlsStream let tls_stream = connector @@ -404,13 +392,11 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastTls { .map_err(|e| zerror!("Cannot create a new TLS listener on {addr}. {e}"))?; // Initialize the TcpListener - let socket = TcpListener::bind(addr) - .await + let (socket, local_addr) = tls_server_config + .tcp_socket_config + .new_listener(&addr) .map_err(|e| zerror!("Can not create a new TLS listener on {}: {}", addr, e))?; - let local_addr = socket - .local_addr() - .map_err(|e| zerror!("Can not create a new TLS listener on {}: {}", addr, e))?; let local_port = local_addr.port(); // Initialize the TlsAcceptor diff --git a/io/zenoh-links/zenoh-link-tls/src/utils.rs b/io/zenoh-links/zenoh-link-tls/src/utils.rs index 74e7cc9e51..e85be4573f 100644 --- a/io/zenoh-links/zenoh-link-tls/src/utils.rs +++ b/io/zenoh-links/zenoh-link-tls/src/utils.rs @@ -31,7 +31,10 @@ use rustls_pki_types::ServerName; use secrecy::ExposeSecret; use webpki::anchor_from_trusted_cert; use zenoh_config::Config as ZenohConfig; -use zenoh_link_commons::{tls::WebPkiVerifierAnyServerName, ConfigurationInspector}; +use zenoh_link_commons::{ + tcp::TcpSocketConfig, tls::WebPkiVerifierAnyServerName, ConfigurationInspector, + TCP_RX_BUFFER_SIZE, TCP_TX_BUFFER_SIZE, +}; use zenoh_protocol::core::{ endpoint::{Address, Config}, parameters, @@ -150,18 +153,32 @@ impl ConfigurationInspector for TlsConfigurator { false => ps.push((TLS_CLOSE_LINK_ON_EXPIRATION, "false")), } + let link_c = config.transport().link(); + let rx_buffer_size; + if let Some(size) = link_c.tcp_rx_buffer { + rx_buffer_size = size.to_string(); + ps.push((TCP_RX_BUFFER_SIZE, &rx_buffer_size)); + } + + let tx_buffer_size; + if let Some(size) = link_c.tcp_tx_buffer { + tx_buffer_size = size.to_string(); + ps.push((TCP_TX_BUFFER_SIZE, &tx_buffer_size)); + } + Ok(parameters::from_iter(ps.drain(..))) } } -pub(crate) struct TlsServerConfig { +pub(crate) struct TlsServerConfig<'a> { pub(crate) server_config: ServerConfig, pub(crate) tls_handshake_timeout: Duration, pub(crate) tls_close_link_on_expiration: bool, + pub(crate) tcp_socket_config: TcpSocketConfig<'a>, } -impl TlsServerConfig { - pub async fn new(config: &Config<'_>) -> ZResult { +impl<'a> TlsServerConfig<'a> { + pub async fn new(config: &Config<'a>) -> ZResult { let tls_server_client_auth: bool = match config.get(TLS_ENABLE_MTLS) { Some(s) => s .parse() @@ -241,10 +258,27 @@ impl TlsServerConfig { .unwrap_or(config::TLS_HANDSHAKE_TIMEOUT_MS_DEFAULT), ); + let mut tcp_rx_buffer_size = None; + if let Some(size) = config.get(TCP_RX_BUFFER_SIZE) { + tcp_rx_buffer_size = Some( + size.parse() + .map_err(|_| zerror!("Unknown TCP read buffer size argument: {}", size))?, + ); + }; + let mut tcp_tx_buffer_size = None; + if let Some(size) = config.get(TCP_TX_BUFFER_SIZE) { + tcp_tx_buffer_size = Some( + size.parse() + .map_err(|_| zerror!("Unknown TCP write buffer size argument: {}", size))?, + ); + }; + Ok(TlsServerConfig { server_config: sc, tls_handshake_timeout, tls_close_link_on_expiration, + // TODO: add interface binding + tcp_socket_config: TcpSocketConfig::new(tcp_tx_buffer_size, tcp_rx_buffer_size, None), }) } @@ -269,13 +303,14 @@ impl TlsServerConfig { } } -pub(crate) struct TlsClientConfig { +pub(crate) struct TlsClientConfig<'a> { pub(crate) client_config: ClientConfig, pub(crate) tls_close_link_on_expiration: bool, + pub(crate) tcp_socket_config: TcpSocketConfig<'a>, } -impl TlsClientConfig { - pub async fn new(config: &Config<'_>) -> ZResult { +impl<'a> TlsClientConfig<'a> { + pub async fn new(config: &Config<'a>) -> ZResult { let tls_client_server_auth: bool = match config.get(TLS_ENABLE_MTLS) { Some(s) => s .parse() @@ -386,9 +421,27 @@ impl TlsClientConfig { .with_no_client_auth() } }; + + let mut tcp_rx_buffer_size = None; + if let Some(size) = config.get(TCP_RX_BUFFER_SIZE) { + tcp_rx_buffer_size = Some( + size.parse() + .map_err(|_| zerror!("Unknown TCP read buffer size argument: {}", size))?, + ); + }; + let mut tcp_tx_buffer_size = None; + if let Some(size) = config.get(TCP_TX_BUFFER_SIZE) { + tcp_tx_buffer_size = Some( + size.parse() + .map_err(|_| zerror!("Unknown TCP write buffer size argument: {}", size))?, + ); + }; + Ok(TlsClientConfig { client_config: cc, tls_close_link_on_expiration, + // TODO: add interface binding + tcp_socket_config: TcpSocketConfig::new(tcp_tx_buffer_size, tcp_rx_buffer_size, None), }) } diff --git a/zenoh/tests/tcp_buffers.rs b/zenoh/tests/tcp_buffers.rs new file mode 100644 index 0000000000..8d75682507 --- /dev/null +++ b/zenoh/tests/tcp_buffers.rs @@ -0,0 +1,111 @@ +// +// 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::{Config, Wait}; + +#[test] +fn buffer_size_config() { + let mut config = Config::default(); + config + .insert_json5( + "transport/link", + r#" + { + tcp_tx_buffer: 65000, + tcp_rx_buffer: 65000, + } + "#, + ) + .unwrap(); + + config + .insert_json5("listen/endpoints", r#"["tcp/[::]:0"]"#) + .unwrap(); + + zenoh::open(config).wait().unwrap(); +} + +#[test] +fn buffer_size_endpoint() { + let mut config = Config::default(); + config + .insert_json5( + "listen/endpoints", + r#"["tcp/[::]:0#tcp_tx_buffer=65000;tcp_rx_buffer=65000"]"#, + ) + .unwrap(); + + zenoh::open(config).wait().unwrap(); +} + +#[cfg(target_os = "macos")] +#[test] +#[should_panic(expected = "Can not create a new TCP listener")] +fn buffer_size_override() { + buffer_size_config_override(); +} + +#[cfg(not(target_os = "macos"))] +#[test] +fn buffer_size_override() { + buffer_size_config_override(); +} + +fn buffer_size_config_override() { + let mut config = Config::default(); + config + .insert_json5( + "transport/link", + r#" + { + tcp_tx_buffer: 0, + tcp_rx_buffer: 0, + } + "#, + ) + .unwrap(); + + config + .insert_json5( + "listen/endpoints", + r#"["tcp/[::]:0#tcp_tx_buffer=65000;tcp_rx_buffer=65000"]"#, + ) + .unwrap(); + + zenoh::open(config).wait().unwrap(); +} + +#[cfg(target_os = "macos")] +#[test] +#[should_panic(expected = "Can not create a new TCP listener")] +fn buffer_size_zero() { + listen_zero_buffers(); +} + +#[cfg(not(target_os = "macos"))] +#[test] +fn buffer_size_zero() { + listen_zero_buffers(); +} + +fn listen_zero_buffers() { + let mut config = Config::default(); + config + .insert_json5( + "listen/endpoints", + r#"["tcp/[::]:0#tcp_tx_buffer=0;tcp_rx_buffer=0"]"#, + ) + .unwrap(); + zenoh::open(config).wait().unwrap(); +} From 65a0c044d7b546fc7d47daca5e9e6ee3724648bc Mon Sep 17 00:00:00 2001 From: Gabriele Baldoni Date: Fri, 6 Dec 2024 16:49:03 +0100 Subject: [PATCH 02/23] chore: enable rest plugin only if in config or if --rest-http-port is set (#1593) * chore: enable rest plugin only if in config or if --rest-http-port is passed Signed-off-by: Gabriele Baldoni * Update README with --rest-http-port option --------- Signed-off-by: Gabriele Baldoni Co-authored-by: Luca Cominardi --- README.md | 6 +++--- zenohd/src/main.rs | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8020c2d0b2..58eb84f803 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Zenoh's router is built as `target/release/zenohd`. All the examples are built i * run the Zenoh router with permission to perform config changes via the admin space, and with a memory storage: ```sh - ./target/release/zenohd --adminspace-permissions=rw --cfg='plugins/storage_manager/storages/demo:{key_expr:"demo/example/**",volume:"memory"}' + ./target/release/zenohd --rest-http-port=8000 --adminspace-permissions=rw --cfg='plugins/storage_manager/storages/demo:{key_expr:"demo/example/**",volume:"memory"}' ``` * in another shell, get info of the zenoh router via the zenoh admin space (you may use `jq` for pretty json formatting): @@ -246,11 +246,11 @@ By default the Zenoh router is delivered or built with 2 plugins. These may be c > [!WARNING] > Since `v0.6`, `zenohd` no longer loads every available plugin at startup. Instead, only configured plugins are loaded (after processing `--cfg` and `--plugin` options). Once `zenohd` is running, plugins can be hot-loaded and, if they support it, reconfigured at runtime by editing their configuration through the adminspace. -Note that the REST plugin is added to the configuration by the default value of the `--rest-http-port` CLI argument. - **[REST plugin](https://zenoh.io/docs/manual/plugin-http/)** (exposing a REST API): This plugin converts GET and PUT REST requests into Zenoh gets and puts respectively. +Note that to activate the REST plugin on `zenohd` the CLI argument should be passed: `--rest-http-port=8000` (or any other port of your choice). + **[Storages plugin](https://zenoh.io/docs/manual/plugin-storage-manager/)** (managing [backends and storages](https://zenoh.io/docs/manual/plugin-storage-manager/#backends-and-volumes)) This plugin allows you to easily define storages. These will store key-value pairs they subscribed to, and send the most recent ones when queried. Check out [DEFAULT_CONFIG.json5](DEFAULT_CONFIG.json5) for info on how to configure them. diff --git a/zenohd/src/main.rs b/zenohd/src/main.rs index 0e5e9737ee..3432335136 100644 --- a/zenohd/src/main.rs +++ b/zenohd/src/main.rs @@ -116,9 +116,8 @@ fn config_from_args(args: &Args) -> Config { if let Some(id) = &args.id { config.set_id(id.parse().unwrap()).unwrap(); } - // apply '--rest-http-port' to config only if explicitly set (overwriting config), - // or if no config file is set (to apply its default value) - if args.rest_http_port.is_some() || args.config.is_none() { + // apply '--rest-http-port' to config only if explicitly set (overwriting config) + if args.rest_http_port.is_some() { let value = args.rest_http_port.as_deref().unwrap_or("8000"); if !value.eq_ignore_ascii_case("none") { config From 233240622f548c18fe41450144010ad1b7e1fe17 Mon Sep 17 00:00:00 2001 From: Oussama Teffahi <70609372+oteffahi@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:01:55 +0100 Subject: [PATCH 03/23] Fix overlap of keyexpr between qos tests (#1636) --- zenoh/tests/qos.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zenoh/tests/qos.rs b/zenoh/tests/qos.rs index 14a3f2b657..4a29858e31 100644 --- a/zenoh/tests/qos.rs +++ b/zenoh/tests/qos.rs @@ -74,7 +74,7 @@ async fn qos_pubsub_overwrite_config() { qos: { publication: [ { - key_exprs: ["test/qos/overwritten", "test/not_applicable/**"], + key_exprs: ["test/qos_overwrite/overwritten", "test/not_applicable/**"], config: { congestion_control: "drop", express: false, @@ -100,14 +100,14 @@ async fn qos_pubsub_overwrite_config() { let session1 = ztimeout!(zenoh::open(qos_config_overwrite)).unwrap(); let session2 = ztimeout!(zenoh::open(zenoh::Config::default())).unwrap(); - let subscriber = ztimeout!(session2.declare_subscriber("test/qos/**")).unwrap(); + let subscriber = ztimeout!(session2.declare_subscriber("test/qos_overwrite/**")).unwrap(); tokio::time::sleep(SLEEP).await; // Session API tests // Session API - overwritten PUT ztimeout!(session1 - .put("test/qos/overwritten", "qos") + .put("test/qos_overwrite/overwritten", "qos") .congestion_control(CongestionControl::Block) .priority(Priority::DataLow) .express(true) @@ -123,7 +123,7 @@ async fn qos_pubsub_overwrite_config() { // Session API - overwritten DELETE ztimeout!(session1 - .delete("test/qos/overwritten") + .delete("test/qos_overwrite/overwritten") .congestion_control(CongestionControl::Block) .priority(Priority::DataLow) .express(true) @@ -139,7 +139,7 @@ async fn qos_pubsub_overwrite_config() { // Session API - non-overwritten PUT ztimeout!(session1 - .put("test/qos/no_overwrite", "qos") + .put("test/qos_overwrite/no_overwrite", "qos") .congestion_control(CongestionControl::Block) .priority(Priority::DataLow) .express(true) @@ -154,7 +154,7 @@ async fn qos_pubsub_overwrite_config() { // Session API - non-overwritten DELETE ztimeout!(session1 - .delete("test/qos/no_overwrite") + .delete("test/qos_overwrite/no_overwrite") .congestion_control(CongestionControl::Block) .priority(Priority::DataLow) .express(true) @@ -170,7 +170,7 @@ async fn qos_pubsub_overwrite_config() { // Publisher API tests let overwrite_config_publisher = ztimeout!(session1 - .declare_publisher("test/qos/overwritten") + .declare_publisher("test/qos_overwrite/overwritten") .congestion_control(CongestionControl::Block) .priority(Priority::DataLow) .express(true) @@ -179,7 +179,7 @@ async fn qos_pubsub_overwrite_config() { .unwrap(); let no_overwrite_config_publisher = ztimeout!(session1 - .declare_publisher("test/qos/no_overwrite") + .declare_publisher("test/qos_overwrite/no_overwrite") .congestion_control(CongestionControl::Block) .priority(Priority::DataLow) .express(true) From 37491bbd86683641b50d35f08a1cd6365608a37f Mon Sep 17 00:00:00 2001 From: Julien Enoch Date: Mon, 9 Dec 2024 15:27:06 +0100 Subject: [PATCH 04/23] DEFAULT_CONFIG.json5: add missing default config for autoconnect.client (#1642) * DEFAULT_CONFIG.json5: add autoconnect.client default value * DEFAULT_CONFIG.json5: clarify 'autoconnect' description --- DEFAULT_CONFIG.json5 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DEFAULT_CONFIG.json5 b/DEFAULT_CONFIG.json5 index 361b8de7fa..6b3e5ee7b2 100644 --- a/DEFAULT_CONFIG.json5 +++ b/DEFAULT_CONFIG.json5 @@ -128,10 +128,10 @@ /// The time-to-live on multicast scouting packets ttl: 1, /// Which type of Zenoh instances to automatically establish sessions with upon discovery on UDP multicast. - /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) - /// or different values for router, peer and client (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). + /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router, peer or client mode (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). /// Each value is a list of: "peer", "router" and/or "client". - autoconnect: { router: [], peer: ["router", "peer"] }, + autoconnect: { router: [], peer: ["router", "peer"], client: ["router", "peer"] }, /// Whether or not to listen for scout messages on UDP multicast and reply to them. listen: true, }, @@ -146,10 +146,10 @@ /// direct connectivity with each other. multihop: false, /// Which type of Zenoh instances to automatically establish sessions with upon discovery on gossip. - /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) - /// or different values for router, peer and client (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). + /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router, peer or client mode (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). /// Each value is a list of: "peer", "router" and/or "client". - autoconnect: { router: [], peer: ["router", "peer"] }, + autoconnect: { router: [], peer: ["router", "peer"], client: ["router", "peer"] }, }, }, From 74c7732e8b24c10a19094fda80bafad0eb2b0075 Mon Sep 17 00:00:00 2001 From: Oussama Teffahi <70609372+oteffahi@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:41:59 +0100 Subject: [PATCH 05/23] Prioritize endpoint config over configuration file (#1635) * Overwrite incoming config with current endpoint config * Update affected tests * Change overwrite behavior in transport logic instead of Endpoint This reverts commit 5157987f8cb6c4eb812cf7b26baf39df63627a4f. * Remove mistakenly added files --- io/zenoh-links/zenoh-link-tls/src/utils.rs | 20 +++++++++++------ io/zenoh-transport/src/multicast/manager.rs | 12 ++++++++--- io/zenoh-transport/src/unicast/manager.rs | 24 +++++++++++++++------ zenoh/tests/tcp_buffers.rs | 14 +----------- 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/io/zenoh-links/zenoh-link-tls/src/utils.rs b/io/zenoh-links/zenoh-link-tls/src/utils.rs index e85be4573f..9d96fbda64 100644 --- a/io/zenoh-links/zenoh-link-tls/src/utils.rs +++ b/io/zenoh-links/zenoh-link-tls/src/utils.rs @@ -32,7 +32,7 @@ use secrecy::ExposeSecret; use webpki::anchor_from_trusted_cert; use zenoh_config::Config as ZenohConfig; use zenoh_link_commons::{ - tcp::TcpSocketConfig, tls::WebPkiVerifierAnyServerName, ConfigurationInspector, + tcp::TcpSocketConfig, tls::WebPkiVerifierAnyServerName, ConfigurationInspector, BIND_INTERFACE, TCP_RX_BUFFER_SIZE, TCP_TX_BUFFER_SIZE, }; use zenoh_protocol::core::{ @@ -178,7 +178,7 @@ pub(crate) struct TlsServerConfig<'a> { } impl<'a> TlsServerConfig<'a> { - pub async fn new(config: &Config<'a>) -> ZResult { + pub async fn new(config: &'a Config<'_>) -> ZResult { let tls_server_client_auth: bool = match config.get(TLS_ENABLE_MTLS) { Some(s) => s .parse() @@ -277,8 +277,11 @@ impl<'a> TlsServerConfig<'a> { server_config: sc, tls_handshake_timeout, tls_close_link_on_expiration, - // TODO: add interface binding - tcp_socket_config: TcpSocketConfig::new(tcp_tx_buffer_size, tcp_rx_buffer_size, None), + tcp_socket_config: TcpSocketConfig::new( + tcp_tx_buffer_size, + tcp_rx_buffer_size, + config.get(BIND_INTERFACE), + ), }) } @@ -310,7 +313,7 @@ pub(crate) struct TlsClientConfig<'a> { } impl<'a> TlsClientConfig<'a> { - pub async fn new(config: &Config<'a>) -> ZResult { + pub async fn new(config: &'a Config<'_>) -> ZResult { let tls_client_server_auth: bool = match config.get(TLS_ENABLE_MTLS) { Some(s) => s .parse() @@ -440,8 +443,11 @@ impl<'a> TlsClientConfig<'a> { Ok(TlsClientConfig { client_config: cc, tls_close_link_on_expiration, - // TODO: add interface binding - tcp_socket_config: TcpSocketConfig::new(tcp_tx_buffer_size, tcp_rx_buffer_size, None), + tcp_socket_config: TcpSocketConfig::new( + tcp_tx_buffer_size, + tcp_rx_buffer_size, + config.get(BIND_INTERFACE), + ), }) } diff --git a/io/zenoh-transport/src/multicast/manager.rs b/io/zenoh-transport/src/multicast/manager.rs index e2899b1d1a..8046d5bd58 100644 --- a/io/zenoh-transport/src/multicast/manager.rs +++ b/io/zenoh-transport/src/multicast/manager.rs @@ -256,9 +256,15 @@ impl TransportManager { .await?; // Fill and merge the endpoint configuration if let Some(config) = self.config.endpoints.get(endpoint.protocol().as_str()) { - endpoint - .config_mut() - .extend_from_iter(parameters::iter(config))?; + let mut config = parameters::Parameters::from(config.as_str()); + // Overwrite config with current endpoint parameters + config.extend_from_iter(endpoint.config().iter()); + endpoint = EndPoint::new( + endpoint.protocol(), + endpoint.address(), + endpoint.metadata(), + config.as_str(), + )?; } // Open the link diff --git a/io/zenoh-transport/src/unicast/manager.rs b/io/zenoh-transport/src/unicast/manager.rs index 5511451b3d..7497604757 100644 --- a/io/zenoh-transport/src/unicast/manager.rs +++ b/io/zenoh-transport/src/unicast/manager.rs @@ -385,9 +385,15 @@ impl TransportManager { .await?; // Fill and merge the endpoint configuration if let Some(config) = self.config.endpoints.get(endpoint.protocol().as_str()) { - endpoint - .config_mut() - .extend_from_iter(parameters::iter(config))?; + let mut config = parameters::Parameters::from(config.as_str()); + // Overwrite config with current endpoint parameters + config.extend_from_iter(endpoint.config().iter()); + endpoint = EndPoint::new( + endpoint.protocol(), + endpoint.address(), + endpoint.metadata(), + config.as_str(), + )?; }; manager.new_listener(endpoint).await } @@ -705,9 +711,15 @@ impl TransportManager { .await?; // Fill and merge the endpoint configuration if let Some(config) = self.config.endpoints.get(endpoint.protocol().as_str()) { - endpoint - .config_mut() - .extend_from_iter(parameters::iter(config))?; + let mut config = parameters::Parameters::from(config.as_str()); + // Overwrite config with current endpoint parameters + config.extend_from_iter(endpoint.config().iter()); + endpoint = EndPoint::new( + endpoint.protocol(), + endpoint.address(), + endpoint.metadata(), + config.as_str(), + )?; }; // Create a new link associated by calling the Link Manager diff --git a/zenoh/tests/tcp_buffers.rs b/zenoh/tests/tcp_buffers.rs index 8d75682507..c443f14894 100644 --- a/zenoh/tests/tcp_buffers.rs +++ b/zenoh/tests/tcp_buffers.rs @@ -49,20 +49,8 @@ fn buffer_size_endpoint() { zenoh::open(config).wait().unwrap(); } -#[cfg(target_os = "macos")] -#[test] -#[should_panic(expected = "Can not create a new TCP listener")] -fn buffer_size_override() { - buffer_size_config_override(); -} - -#[cfg(not(target_os = "macos"))] #[test] -fn buffer_size_override() { - buffer_size_config_override(); -} - -fn buffer_size_config_override() { +fn buffer_size_endpoint_overwrite() { let mut config = Config::default(); config .insert_json5( From b5ca078bae939a6968df2d3e35f1d5d009d8062f Mon Sep 17 00:00:00 2001 From: Oussama Teffahi <70609372+oteffahi@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:09:01 +0100 Subject: [PATCH 06/23] Update TCP buffer size config (#1648) * Rename TCP buffer size parameters, duplicate config for TLS and TCP * Update tcp buffer size tests * Update DEFAULT_CONFIG, add more examples * Update DEFAULT_CONFIG --- DEFAULT_CONFIG.json5 | 27 +++++++++++++++++----- commons/zenoh-config/src/lib.rs | 15 ++++++++---- io/zenoh-link-commons/src/lib.rs | 4 ++-- io/zenoh-links/zenoh-link-tcp/src/utils.rs | 17 +++++++------- io/zenoh-links/zenoh-link-tls/src/utils.rs | 19 ++++++++------- zenoh/tests/tcp_buffers.rs | 18 +++++++-------- 6 files changed, 60 insertions(+), 40 deletions(-) diff --git a/DEFAULT_CONFIG.json5 b/DEFAULT_CONFIG.json5 index 6b3e5ee7b2..552107328f 100644 --- a/DEFAULT_CONFIG.json5 +++ b/DEFAULT_CONFIG.json5 @@ -25,6 +25,9 @@ /// /// It is also possible to specify a priority range and/or a reliability setting to be used on the link. /// For example `tcp/localhost?prio=6-7;rel=0` assigns priorities "data_low" and "background" to the established link. + /// + /// For TCP and TLS links, it is possible to specify the TCP buffer sizes: + /// E.g. tcp/192.168.0.1:7447#so_sndbuf=65000;so_rcvbuf=65000 connect: { /// timeout waiting for all endpoints connected (0: no retry, -1: infinite timeout) /// Accepts a single value (e.g. timeout_ms: 0) @@ -68,6 +71,9 @@ /// /// It is also possible to specify a priority range and/or a reliability setting to be used on the link. /// For example `tcp/localhost?prio=6-7;rel=0` assigns priorities "data_low" and "background" to the established link. + /// + /// For TCP and TLS links, it is possible to specify the TCP buffer sizes: + /// E.g. tcp/192.168.0.1:7447#so_sndbuf=65000;so_rcvbuf=65000 listen: { /// timeout waiting for all listen endpoints (0: no retry, -1: infinite timeout) /// Accepts a single value (e.g. timeout_ms: 0) @@ -494,13 +500,22 @@ // If set to true, links that require certificates (tls/quic) will automatically disconnect when the time of expiration of the remote certificate chain is reached // note that mTLS (client authentication) is required for a listener to disconnect a client on expiration close_link_on_expiration: false, + /// Optional configuration for TCP system buffers sizes for TLS links + /// + /// Configure TCP read buffer size (bytes) + // so_rcvbuf: 123456, + /// Configure TCP write buffer size (bytes) + // so_sndbuf: 123456, }, - /// Optional configuration for TCP system buffers sizes. Applies to TCP and TLS links. - /// - /// Configure TCP read buffer size (bytes) - // tcp_rx_buffer: 123456, - /// Configure TCP write buffer size (bytes) - // tcp_tx_buffer: 123456, + // // Configure optional TCP link specific parameters + // tcp: { + // /// Optional configuration for TCP system buffers sizes for TCP links + // /// + // /// Configure TCP read buffer size (bytes) + // // so_rcvbuf: 123456, + // /// Configure TCP write buffer size (bytes) + // // so_sndbuf: 123456, + // } }, /// Shared memory configuration. /// NOTE: shared memory can be used only if zenoh is compiled with "shared-memory" feature, otherwise diff --git a/commons/zenoh-config/src/lib.rs b/commons/zenoh-config/src/lib.rs index f0ba50d0a6..8ab4d5d3a6 100644 --- a/commons/zenoh-config/src/lib.rs +++ b/commons/zenoh-config/src/lib.rs @@ -499,6 +499,10 @@ validated_struct::validator! { connect_certificate: Option, verify_name_on_connect: Option, close_link_on_expiration: Option, + /// Configure TCP write buffer size + pub so_sndbuf: Option, + /// Configure TCP read buffer size + pub so_rcvbuf: Option, // Skip serializing field because they contain secrets #[serde(skip_serializing)] root_ca_certificate_base64: Option, @@ -511,14 +515,17 @@ validated_struct::validator! { #[serde(skip_serializing)] connect_certificate_base64 : Option, }, + pub tcp: #[derive(Default)] + TcpConf { + /// Configure TCP write buffer size + pub so_sndbuf: Option, + /// Configure TCP read buffer size + pub so_rcvbuf: Option, + }, pub unixpipe: #[derive(Default)] UnixPipeConf { file_access_mask: Option }, - /// Configure TCP read buffer size - pub tcp_rx_buffer: Option, - /// Configure TCP write buffer size - pub tcp_tx_buffer: Option, }, pub shared_memory: ShmConf { diff --git a/io/zenoh-link-commons/src/lib.rs b/io/zenoh-link-commons/src/lib.rs index ed6f5e72f8..6165a36a31 100644 --- a/io/zenoh-link-commons/src/lib.rs +++ b/io/zenoh-link-commons/src/lib.rs @@ -45,8 +45,8 @@ use zenoh_result::ZResult; /*************************************/ pub const BIND_INTERFACE: &str = "iface"; -pub const TCP_TX_BUFFER_SIZE: &str = "tcp_tx_buffer"; -pub const TCP_RX_BUFFER_SIZE: &str = "tcp_rx_buffer"; +pub const TCP_SO_SND_BUF: &str = "so_sndbuf"; +pub const TCP_SO_RCV_BUF: &str = "so_rcvbuf"; #[derive(Clone, Debug, Serialize, Hash, PartialEq, Eq)] pub struct Link { diff --git a/io/zenoh-links/zenoh-link-tcp/src/utils.rs b/io/zenoh-links/zenoh-link-tcp/src/utils.rs index 4969693193..6772bc8f9a 100644 --- a/io/zenoh-links/zenoh-link-tcp/src/utils.rs +++ b/io/zenoh-links/zenoh-link-tcp/src/utils.rs @@ -13,8 +13,7 @@ // use zenoh_config::Config as ZenohConfig; use zenoh_link_commons::{ - tcp::TcpSocketConfig, ConfigurationInspector, BIND_INTERFACE, TCP_RX_BUFFER_SIZE, - TCP_TX_BUFFER_SIZE, + tcp::TcpSocketConfig, ConfigurationInspector, BIND_INTERFACE, TCP_SO_RCV_BUF, TCP_SO_SND_BUF, }; use zenoh_protocol::core::{parameters, Config}; use zenoh_result::{zerror, ZResult}; @@ -25,17 +24,17 @@ pub struct TcpConfigurator; impl ConfigurationInspector for TcpConfigurator { fn inspect_config(&self, config: &ZenohConfig) -> ZResult { let mut ps: Vec<(&str, &str)> = vec![]; - let c = config.transport().link(); + let c = config.transport().link().tcp(); let rx_buffer_size; - if let Some(size) = c.tcp_rx_buffer { + if let Some(size) = c.so_rcvbuf() { rx_buffer_size = size.to_string(); - ps.push((TCP_RX_BUFFER_SIZE, &rx_buffer_size)); + ps.push((TCP_SO_RCV_BUF, &rx_buffer_size)); } let tx_buffer_size; - if let Some(size) = c.tcp_tx_buffer { + if let Some(size) = c.so_sndbuf() { tx_buffer_size = size.to_string(); - ps.push((TCP_TX_BUFFER_SIZE, &tx_buffer_size)); + ps.push((TCP_SO_SND_BUF, &tx_buffer_size)); } Ok(parameters::from_iter(ps.drain(..))) @@ -56,13 +55,13 @@ impl<'a> TcpLinkConfig<'a> { bind_iface: config.get(BIND_INTERFACE), }; - if let Some(size) = config.get(TCP_RX_BUFFER_SIZE) { + if let Some(size) = config.get(TCP_SO_RCV_BUF) { tcp_config.rx_buffer_size = Some( size.parse() .map_err(|_| zerror!("Unknown TCP read buffer size argument: {}", size))?, ); }; - if let Some(size) = config.get(TCP_TX_BUFFER_SIZE) { + if let Some(size) = config.get(TCP_SO_SND_BUF) { tcp_config.tx_buffer_size = Some( size.parse() .map_err(|_| zerror!("Unknown TCP write buffer size argument: {}", size))?, diff --git a/io/zenoh-links/zenoh-link-tls/src/utils.rs b/io/zenoh-links/zenoh-link-tls/src/utils.rs index 9d96fbda64..32a5f929a7 100644 --- a/io/zenoh-links/zenoh-link-tls/src/utils.rs +++ b/io/zenoh-links/zenoh-link-tls/src/utils.rs @@ -33,7 +33,7 @@ use webpki::anchor_from_trusted_cert; use zenoh_config::Config as ZenohConfig; use zenoh_link_commons::{ tcp::TcpSocketConfig, tls::WebPkiVerifierAnyServerName, ConfigurationInspector, BIND_INTERFACE, - TCP_RX_BUFFER_SIZE, TCP_TX_BUFFER_SIZE, + TCP_SO_RCV_BUF, TCP_SO_SND_BUF, }; use zenoh_protocol::core::{ endpoint::{Address, Config}, @@ -153,17 +153,16 @@ impl ConfigurationInspector for TlsConfigurator { false => ps.push((TLS_CLOSE_LINK_ON_EXPIRATION, "false")), } - let link_c = config.transport().link(); let rx_buffer_size; - if let Some(size) = link_c.tcp_rx_buffer { + if let Some(size) = c.so_rcvbuf() { rx_buffer_size = size.to_string(); - ps.push((TCP_RX_BUFFER_SIZE, &rx_buffer_size)); + ps.push((TCP_SO_RCV_BUF, &rx_buffer_size)); } let tx_buffer_size; - if let Some(size) = link_c.tcp_tx_buffer { + if let Some(size) = c.so_sndbuf() { tx_buffer_size = size.to_string(); - ps.push((TCP_TX_BUFFER_SIZE, &tx_buffer_size)); + ps.push((TCP_SO_SND_BUF, &tx_buffer_size)); } Ok(parameters::from_iter(ps.drain(..))) @@ -259,14 +258,14 @@ impl<'a> TlsServerConfig<'a> { ); let mut tcp_rx_buffer_size = None; - if let Some(size) = config.get(TCP_RX_BUFFER_SIZE) { + if let Some(size) = config.get(TCP_SO_RCV_BUF) { tcp_rx_buffer_size = Some( size.parse() .map_err(|_| zerror!("Unknown TCP read buffer size argument: {}", size))?, ); }; let mut tcp_tx_buffer_size = None; - if let Some(size) = config.get(TCP_TX_BUFFER_SIZE) { + if let Some(size) = config.get(TCP_SO_SND_BUF) { tcp_tx_buffer_size = Some( size.parse() .map_err(|_| zerror!("Unknown TCP write buffer size argument: {}", size))?, @@ -426,14 +425,14 @@ impl<'a> TlsClientConfig<'a> { }; let mut tcp_rx_buffer_size = None; - if let Some(size) = config.get(TCP_RX_BUFFER_SIZE) { + if let Some(size) = config.get(TCP_SO_RCV_BUF) { tcp_rx_buffer_size = Some( size.parse() .map_err(|_| zerror!("Unknown TCP read buffer size argument: {}", size))?, ); }; let mut tcp_tx_buffer_size = None; - if let Some(size) = config.get(TCP_TX_BUFFER_SIZE) { + if let Some(size) = config.get(TCP_SO_SND_BUF) { tcp_tx_buffer_size = Some( size.parse() .map_err(|_| zerror!("Unknown TCP write buffer size argument: {}", size))?, diff --git a/zenoh/tests/tcp_buffers.rs b/zenoh/tests/tcp_buffers.rs index c443f14894..8d9bb4ac84 100644 --- a/zenoh/tests/tcp_buffers.rs +++ b/zenoh/tests/tcp_buffers.rs @@ -19,11 +19,11 @@ fn buffer_size_config() { let mut config = Config::default(); config .insert_json5( - "transport/link", + "transport/link/tcp", r#" { - tcp_tx_buffer: 65000, - tcp_rx_buffer: 65000, + so_sndbuf: 65000, + so_rcvbuf: 65000, } "#, ) @@ -42,7 +42,7 @@ fn buffer_size_endpoint() { config .insert_json5( "listen/endpoints", - r#"["tcp/[::]:0#tcp_tx_buffer=65000;tcp_rx_buffer=65000"]"#, + r#"["tcp/[::]:0#so_sndbuf=65000;so_rcvbuf=65000"]"#, ) .unwrap(); @@ -54,11 +54,11 @@ fn buffer_size_endpoint_overwrite() { let mut config = Config::default(); config .insert_json5( - "transport/link", + "transport/link/tcp", r#" { - tcp_tx_buffer: 0, - tcp_rx_buffer: 0, + so_sndbuf: 0, + so_rcvbuf: 0, } "#, ) @@ -67,7 +67,7 @@ fn buffer_size_endpoint_overwrite() { config .insert_json5( "listen/endpoints", - r#"["tcp/[::]:0#tcp_tx_buffer=65000;tcp_rx_buffer=65000"]"#, + r#"["tcp/[::]:0#so_sndbuf=65000;so_rcvbuf=65000"]"#, ) .unwrap(); @@ -92,7 +92,7 @@ fn listen_zero_buffers() { config .insert_json5( "listen/endpoints", - r#"["tcp/[::]:0#tcp_tx_buffer=0;tcp_rx_buffer=0"]"#, + r#"["tcp/[::]:0#so_sndbuf=0;so_rcvbuf=0"]"#, ) .unwrap(); zenoh::open(config).wait().unwrap(); From fb2d2bc2865beeb49a49337fcfe12654cd013ffc Mon Sep 17 00:00:00 2001 From: Oussama Teffahi <70609372+oteffahi@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:09:22 +0100 Subject: [PATCH 07/23] Fix deadlock in subscriber undeclaration (#1649) * Fix deadlock * Only update matching listener status for last remote subscriber * Remove unnecessary drop after changes * Add unit test for undeclare deadlock --- zenoh/src/api/session.rs | 30 ++++++++++++++++++++---------- zenoh/tests/session.rs | 12 ++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/zenoh/src/api/session.rs b/zenoh/src/api/session.rs index 15b7fb4d7b..013536e540 100644 --- a/zenoh/src/api/session.rs +++ b/zenoh/src/api/session.rs @@ -1574,19 +1574,29 @@ impl SessionInner { }, }), }); + #[cfg(feature = "unstable")] + { + let state = zread!(self.state); + self.update_matching_status( + &state, + &sub_state.key_expr, + MatchingStatusType::Subscribers, + false, + ) + } } } else { drop(state); - } - #[cfg(feature = "unstable")] - { - let state = zread!(self.state); - self.update_matching_status( - &state, - &sub_state.key_expr, - MatchingStatusType::Subscribers, - false, - ) + #[cfg(feature = "unstable")] + { + let state = zread!(self.state); + self.update_matching_status( + &state, + &sub_state.key_expr, + MatchingStatusType::Subscribers, + false, + ) + } } } SubscriberKind::LivelinessSubscriber => { diff --git a/zenoh/tests/session.rs b/zenoh/tests/session.rs index 3e84249b82..5a18378446 100644 --- a/zenoh/tests/session.rs +++ b/zenoh/tests/session.rs @@ -430,3 +430,15 @@ async fn zenoh_session_close_in_background() { }; ztimeout!(close_all); } + +#[cfg(feature = "unstable")] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_undeclare_subscribers_same_keyexpr() { + let key_expr = "test/undeclare/subscribers"; + let session = zenoh::open(zenoh::Config::default()).await.unwrap(); + let sub1 = session.declare_subscriber(key_expr).await.unwrap(); + let sub2 = session.declare_subscriber(key_expr).await.unwrap(); + tokio::time::sleep(SLEEP).await; + ztimeout!(sub1.undeclare()).unwrap(); + ztimeout!(sub2.undeclare()).unwrap(); +} From cb3fa548f26bba223ff2f7fee54892b55ed1d318 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 11 Dec 2024 11:00:44 +0100 Subject: [PATCH 08/23] Add Advanced Pub/Sub feature (#1582) * Expose and use ke macro * Fix SourceInfo publication * Add AdvancedPublisher AdvancedSubscriber and AdvancedSubscriber * Fix doctests * Fix doc warnings * Remove debug trace * Add history test * Fix periodic queries * Remove debug trace * Lower test debug level * Add retransmission tests * Liveliness sub callback shoud increase pending queries counter * Liveliness sub callback shoud spawn periodic queries when enbaled * Add late_joiner test * Only treat pending samples when there are no more pending queries * Apply proper sequencing for history * Improve AdvancedSubscriber * Code reorg * Code reorg * Fix deduplication * Subscribe to liveliness tokens with history * Update builders * Add examples * Fix rustdoc * Move stuff in State * Code reorg * Add smaple_miss_callback * Add sample miss test * Update z_advanced_sub example * Explicit use in examples * Update API * Fix rustdoc * Allow sample miss detection when recovery disabled * Add miss_sample_callback to DataSubscriberBuilderExt * Add sample_miss_detection to PublisherBuilderExt * Add test_advanced_sample_miss test * Deliver sample even when no miss callback * Replace sample_miss_callback with sample_miss_listener * Fix clippy warnings * Fix tests * Add HistoryConf max_samples option * Add HistoryConf max_age option * Use BTreeMap * Add meta_keyexpr option * Add late_joiner_detection and meta_keyexpr options on Subcriber side * Renaming * Fix compilation issues * Remove AdvancedCache from public API * Update Session admin to match AdvancedSub * Gather constants * Fix doc build * Renaming * Mark PublicationCache and QueryingSubscriber as deprecated and remove related examples * Remove z_pub_cache and z_query_sub entries from zenoh-ext examples README * Add z_advanced_pub and z_advanced_sub to zenoh-ext examples Cargo.toml * Add CacheConfig replies_qos option * Call cache directly from publisher * Update doc * Add missing unstable tags * Add missing unstable tags * Add missing unstable tags * Add unstable tag everywhere * Add missing AdvancedSubscriber methods * Fix WeakSession::Session internal function * Expose missing SampleMissListener and related structs * Add AdvancedPublisherBuilderExt::advanced function * Add missing AdvancedPublisherBuilder functions * Fix doctests * Expose Miss struct * impl QoSBuilderTrait for AdvancedPublisherBuilder * Propagate PublisherBuilder values to AdvancedPublisherBuilder * Rename AdvancedSubscriber::close() * Add unstable tags * Add AdvancedSubscriber::detect_publishers function * Remove debug println * Renaming * Add unstable tags * Use std Range * Spawn Timer in a tokio runtime * Fix panic when last_delivered is None * Release lock before calling get * Update key mapping * Improve doc * fix: fix callback API (#1647) * Update doc * Fix ke_liveliness * Fix doc * Fix doc --------- Co-authored-by: Joseph Perez --- Cargo.lock | 23 + Cargo.toml | 1 + commons/zenoh-config/src/wrappers.rs | 10 + commons/zenoh-macros/src/lib.rs | 2 +- zenoh-ext/Cargo.toml | 4 +- zenoh-ext/examples/Cargo.toml | 8 +- zenoh-ext/examples/examples/README.md | 22 +- .../{z_pub_cache.rs => z_advanced_pub.rs} | 51 +- zenoh-ext/examples/examples/z_advanced_sub.rs | 84 ++ zenoh-ext/examples/examples/z_query_sub.rs | 80 - zenoh-ext/src/advanced_cache.rs | 363 +++++ zenoh-ext/src/advanced_publisher.rs | 598 ++++++++ zenoh-ext/src/advanced_subscriber.rs | 1324 +++++++++++++++++ zenoh-ext/src/lib.rs | 18 +- zenoh-ext/src/publication_cache.rs | 41 + zenoh-ext/src/publisher_ext.rs | 73 + zenoh-ext/src/querying_subscriber.rs | 130 ++ zenoh-ext/src/session_ext.rs | 8 + zenoh-ext/src/subscriber_ext.rs | 80 +- zenoh-ext/tests/advanced.rs | 715 +++++++++ zenoh-ext/tests/liveliness.rs | 4 + zenoh/Cargo.toml | 1 + zenoh/src/api/admin.rs | 93 +- zenoh/src/api/builders/publisher.rs | 27 + zenoh/src/api/builders/sample.rs | 51 +- zenoh/src/api/publisher.rs | 5 + zenoh/src/api/session.rs | 24 +- zenoh/src/api/subscriber.rs | 5 + zenoh/src/lib.rs | 17 +- 29 files changed, 3695 insertions(+), 167 deletions(-) rename zenoh-ext/examples/examples/{z_pub_cache.rs => z_advanced_pub.rs} (61%) create mode 100644 zenoh-ext/examples/examples/z_advanced_sub.rs delete mode 100644 zenoh-ext/examples/examples/z_query_sub.rs 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 create mode 100644 zenoh-ext/tests/advanced.rs diff --git a/Cargo.lock b/Cargo.lock index e47d08dd0f..a7198e5862 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", @@ -5201,6 +5222,7 @@ dependencies = [ name = "zenoh-ext" version = "1.0.0-dev" dependencies = [ + "async-trait", "bincode", "flume", "futures", @@ -5209,6 +5231,7 @@ dependencies = [ "serde", "tokio", "tracing", + "uhlc", "zenoh", "zenoh-config", "zenoh-macros", 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/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/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-ext/Cargo.toml b/zenoh-ext/Cargo.toml index 4de255d0d6..4fdf98b8df 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 } @@ -45,7 +46,8 @@ futures = { workspace = true } tracing = { workspace = true } serde = { workspace = true, features = ["default"] } leb128 = { workspace = true } -zenoh = { workspace = true, default-features = false } +uhlc = { workspace = true } +zenoh = { workspace = true, features = ["default"] } zenoh-macros = { workspace = true } [dev-dependencies] diff --git a/zenoh-ext/examples/Cargo.toml b/zenoh-ext/examples/Cargo.toml index 658eba1065..688c044d67 100644 --- a/zenoh-ext/examples/Cargo.toml +++ b/zenoh-ext/examples/Cargo.toml @@ -42,12 +42,12 @@ zenoh-ext = { workspace = true, features = ["unstable"] } zenoh-config = { workspace = true } [[example]] -name = "z_query_sub" -path = "examples/z_query_sub.rs" +name = "z_advanced_pub" +path = "examples/z_advanced_pub.rs" [[example]] -name = "z_pub_cache" -path = "examples/z_pub_cache.rs" +name = "z_advanced_sub" +path = "examples/z_advanced_sub.rs" [[example]] name = "z_member" diff --git a/zenoh-ext/examples/examples/README.md b/zenoh-ext/examples/examples/README.md index 498a1ca6fe..b72e50f816 100644 --- a/zenoh-ext/examples/examples/README.md +++ b/zenoh-ext/examples/examples/README.md @@ -15,31 +15,33 @@ ## Examples description -### z_pub_cache +### z_advanced_pub - 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). + 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_pub_cache + z_advanced_pub ``` or ```bash - z_pub_cache --history 10 + z_advanced_pub --history 10 ``` -### z_query_sub +### z_advanced_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. + 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_query_sub + z_advanced_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. diff --git a/zenoh-ext/examples/examples/z_pub_cache.rs b/zenoh-ext/examples/examples/z_advanced_pub.rs similarity index 61% rename from zenoh-ext/examples/examples/z_pub_cache.rs rename to zenoh-ext/examples/examples/z_advanced_pub.rs index d8e13faec4..6437515c8f 100644 --- a/zenoh-ext/examples/examples/z_pub_cache.rs +++ b/zenoh-ext/examples/examples/z_advanced_pub.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + // // Copyright (c) 2023 ZettaScale Technology // @@ -11,12 +13,10 @@ // 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::{AdvancedPublisherBuilderExt, CacheConfig}; use zenoh_ext_examples::CommonArgs; #[tokio::main] @@ -24,27 +24,26 @@ async fn main() { // Initiate logging zenoh::init_log_from_env_or("error"); - let (config, key_expr, value, history, prefix, complete) = parse_args(); + let (config, key_expr, value, history) = 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!("Declaring AdvancedPublisher on {}", &key_expr); + let publisher = session + .declare_publisher(&key_expr) + .cache(CacheConfig::default().max_samples(history)) + .sample_miss_detection() + .publisher_detection() + .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(); + publisher.put(buf).await.unwrap(); } } @@ -59,36 +58,16 @@ struct Args { #[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, -) { +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, - args.prefix, - args.complete, - ) + (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..5bea70f7d5 --- /dev/null +++ b/zenoh-ext/examples/examples/z_advanced_sub.rs @@ -0,0 +1,84 @@ +// +// 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::{AdvancedSubscriberBuilderExt, HistoryConfig, RecoveryConfig}; +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(HistoryConfig::default().detect_late_publishers()) + .recovery(RecoveryConfig::default().periodic_queries(Some(Duration::from_secs(1)))) + .subscriber_detection() + .await + .unwrap(); + + let miss_listener = subscriber.sample_miss_listener().await.unwrap(); + + println!("Press CTRL-C to quit..."); + 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() + ); + } + }, + } + } +} + +#[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) +} 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/advanced_cache.rs b/zenoh-ext/src/advanced_cache.rs new file mode 100644 index 0000000000..c18add5a59 --- /dev/null +++ b/zenoh-ext/src/advanced_cache.rs @@ -0,0 +1,363 @@ +// +// 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::{ + collections::VecDeque, + future::{IntoFuture, Ready}, + ops::{Bound, RangeBounds}, + sync::{Arc, RwLock}, +}; + +use zenoh::{ + internal::{bail, traits::QoSBuilderTrait}, + key_expr::{ + format::{ke, kedefine}, + keyexpr, KeyExpr, + }, + liveliness::LivelinessToken, + qos::{CongestionControl, Priority}, + query::{Queryable, ZenohParameters}, + sample::{Locality, Sample, SampleBuilder}, + Resolvable, Result as ZResult, Session, Wait, KE_ADV_PREFIX, KE_AT, KE_STARSTAR, +}; + +pub(crate) static KE_UHLC: &keyexpr = ke!("uhlc"); +#[zenoh_macros::unstable] +kedefine!( + pub(crate) ke_liveliness: "@adv/${entity:*}/${zid:*}/${eid:*}/${meta:**}/@/${remaining:**}", +); + +#[zenoh_macros::unstable] +/// Configure replies. +#[derive(Clone, Debug)] +pub struct RepliesConfig { + priority: Priority, + congestion_control: CongestionControl, + is_express: bool, +} + +#[zenoh_macros::unstable] +impl Default for RepliesConfig { + fn default() -> Self { + Self { + priority: Priority::Data, + congestion_control: CongestionControl::Block, + is_express: false, + } + } +} + +#[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] +impl QoSBuilderTrait for RepliesConfig { + #[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 + } +} + +#[derive(Debug, Clone)] +/// Configure an [`AdvancedPublisher`](crate::AdvancedPublisher) cache. +#[zenoh_macros::unstable] +pub struct CacheConfig { + max_samples: usize, + replies_config: RepliesConfig, +} + +#[zenoh_macros::unstable] +impl Default for CacheConfig { + fn default() -> Self { + Self { + max_samples: 1, + replies_config: RepliesConfig::default(), + } + } +} + +#[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_config(mut self, qos: RepliesConfig) -> Self { + self.replies_config = qos; + self + } +} + +/// 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>, + queryable_prefix: Option>>, + queryable_origin: Locality, + history: CacheConfig, + 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>, + ) -> AdvancedCacheBuilder<'a, 'b, 'c> { + AdvancedCacheBuilder { + session, + pub_key_expr, + queryable_prefix: Some(Ok((KE_ADV_PREFIX / KE_STARSTAR / KE_AT).into())), + queryable_origin: Locality::default(), + history: CacheConfig::default(), + liveliness: false, + } + } + + /// Change the prefix used for queryable. + #[zenoh_macros::unstable] + 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 + } + + /// 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) -> (Bound, Bound) { + let mut split = range.split(".."); + 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) +} + +/// [`AdvancedCache`]. +#[zenoh_macros::unstable] +pub struct AdvancedCache { + cache: Arc>>, + max_samples: usize, + _queryable: Queryable<()>, + _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 ("[]/") + 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 max_samples={:?}", + &key_expr, + conf.history, + ); + 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) + .callback({ + let cache = cache.clone(); + move |query| { + let range = query + .parameters() + .get("_sn") + .map(decode_range) + .unwrap_or((Bound::Unbounded, Bound::Unbounded)); + 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 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()) + { + 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( + SampleBuilder::from(sample.clone()) + .congestion_control( + conf.history.replies_config.congestion_control, + ) + .priority(conf.history.replies_config.priority) + .express(conf.history.replies_config.is_express) + .into(), + ) + .wait() + { + tracing::warn!("Error replying to query: {}", e); + } + } + } else { + for sample in queue.iter() { + 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()) + { + 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_config.congestion_control, + ) + .priority(conf.history.replies_config.priority) + .express(conf.history.replies_config.is_express) + .into(), + ) + .wait() + { + tracing::warn!("Error replying to query: {}", e); + } + } + } + } + } else { + tracing::error!("Unable to take AdvancedPublisher cache read lock"); + } + } + }) + .wait()?; + + let token = if conf.liveliness { + Some( + conf.session + .liveliness() + .declare_token(queryable_key_expr) + .wait()?, + ) + } else { + None + }; + + Ok(AdvancedCache { + cache, + max_samples: conf.history.max_samples, + _queryable: queryable, + _token: token, + }) + } + + #[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(); + } + 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 new file mode 100644 index 0000000000..023e509d57 --- /dev/null +++ b/zenoh-ext/src/advanced_publisher.rs @@ -0,0 +1,598 @@ +// +// 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, OptionZBytes, ZBytes}, + internal::{ + bail, + traits::{ + EncodingBuilderTrait, QoSBuilderTrait, SampleBuilderTrait, TimestampBuilderTrait, + }, + }, + key_expr::{keyexpr, KeyExpr}, + liveliness::LivelinessToken, + pubsub::{ + PublicationBuilder, PublicationBuilderDelete, PublicationBuilderPut, Publisher, + PublisherBuilder, + }, + qos::{CongestionControl, Priority, Reliability}, + sample::{Locality, SourceInfo}, + 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 { + 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`"] +#[zenoh_macros::unstable] +pub struct AdvancedPublisherBuilder<'a, 'b, 'c> { + session: &'a Session, + pub_key_expr: ZResult>, + encoding: Encoding, + destination: Locality, + reliability: Reliability, + congestion_control: CongestionControl, + priority: Priority, + is_express: bool, + meta_key_expr: Option>>, + sequencing: Sequencing, + liveliness: bool, + cache: bool, + history: CacheConfig, +} + +#[zenoh_macros::unstable] +impl<'a, 'b, 'c> AdvancedPublisherBuilder<'a, 'b, 'c> { + #[zenoh_macros::unstable] + pub(crate) fn new(builder: PublisherBuilder<'a, 'b>) -> AdvancedPublisherBuilder<'a, 'b, 'c> { + AdvancedPublisherBuilder { + 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, + cache: false, + history: CacheConfig::default(), + } + } + + /// 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 [`AdvancedSubscribers`](crate::AdvancedSubscriber) to detect lost samples and optionally ask for retransimission. + /// + /// 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 + } + + /// 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; + if self.sequencing == Sequencing::None { + self.sequencing = Sequencing::Timestamp; + } + self.history = config; + self + } + + /// Allow this [`AdvancedPublisher`] to be detected by [`AdvancedSubscribers`](crate::AdvancedSubscriber). + /// + /// This allows [`AdvancedSubscribers`](crate::AdvancedSubscriber) to retrieve the local history. + #[zenoh_macros::unstable] + pub fn publisher_detection(mut self) -> Self { + 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. + #[zenoh_macros::unstable] + pub fn publisher_detection_metadata(mut self, meta: TryIntoKeyExpr) -> Self + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + self.meta_key_expr = Some(meta.try_into().map_err(Into::into)); + self + } +} + +#[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] +impl EncodingBuilderTrait for AdvancedPublisherBuilder<'_, '_, '_> { + #[zenoh_macros::unstable] + fn encoding>(self, encoding: T) -> Self { + Self { + encoding: encoding.into(), + ..self + } + } +} + +#[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>; +} + +#[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()) + } +} + +/// [`AdvancedPublisher`]. +#[zenoh_macros::unstable] +pub struct AdvancedPublisher<'a> { + publisher: Publisher<'a>, + seqnum: Option, + cache: Option, + _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 { + Some(meta) => Some(meta?), + None => None, + }; + + let publisher = conf + .session + .declare_publisher(key_expr.clone().into_owned()) + .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 / KE_PUB / &id.zid().into_keyexpr(); + let prefix = match conf.sequencing { + Sequencing::SequenceNumber => { + prefix / &KeyExpr::try_from(id.eid().to_string()).unwrap() + } + _ => prefix / KE_UHLC, + }; + let prefix = match meta { + Some(meta) => prefix / &meta / KE_AT, + // We need this empty chunk because af a routing matching bug + _ => prefix / KE_EMPTY / KE_AT, + }; + + 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 { + Some( + AdvancedCacheBuilder::new(conf.session, Ok(key_expr.clone().into_owned())) + .history(conf.history) + .queryable_prefix(&prefix) + .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, + _token: token, + }) + } + + /// Returns the [`EntityGlobalId`] of this Publisher. + #[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> { + 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() + } + + /// Put data. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh_ext::AdvancedPublisherBuilderExt; + /// + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); + /// publisher.put("value").await.unwrap(); + /// # } + /// ``` + #[inline] + #[zenoh_macros::unstable] + pub fn put(&self, payload: IntoZBytes) -> AdvancedPublisherPutBuilder<'_> + where + IntoZBytes: Into, + { + let mut builder = self.publisher.put(payload); + if let Some(seqnum) = &self.seqnum { + builder = builder.source_info(SourceInfo::new( + Some(self.publisher.id()), + Some(seqnum.fetch_add(1, Ordering::Relaxed)), + )); + } + if let Some(hlc) = self.publisher.session().hlc() { + builder = builder.timestamp(hlc.new_timestamp()); + } + AdvancedPublisherPutBuilder { + builder, + cache: self.cache.as_ref(), + } + } + + /// Delete data. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh_ext::AdvancedPublisherBuilderExt; + /// + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); + /// publisher.delete().await.unwrap(); + /// # } + /// ``` + #[zenoh_macros::unstable] + pub fn delete(&self) -> AdvancedPublisherDeleteBuilder<'_> { + let mut builder = self.publisher.delete(); + if let Some(seqnum) = &self.seqnum { + builder = builder.source_info(SourceInfo::new( + Some(self.publisher.id()), + Some(seqnum.fetch_add(1, Ordering::Relaxed)), + )); + } + 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. + /// + /// [`MatchingStatus::matching`](zenoh::matching::MatchingStatus::matching) + /// will return true if there exist Subscribers matching the Publisher's key expression and false otherwise. + /// + /// # Examples + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh_ext::AdvancedPublisherBuilderExt; + /// + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); + /// let matching_subscribers: bool = publisher + /// .matching_status() + /// .await + /// .unwrap() + /// .matching(); + /// # } + /// ``` + #[zenoh_macros::unstable] + pub fn matching_status(&self) -> impl Resolve> + '_ { + self.publisher.matching_status() + } + + /// Return a [`MatchingListener`](zenoh::matching::MatchingStatus) for this Publisher. + /// + /// The [`MatchingListener`](zenoh::matching::MatchingStatus) that will send a notification each time + /// the [`MatchingStatus`](zenoh::matching::MatchingStatus) of the Publisher changes. + /// + /// # Examples + /// ```no_run + /// # #[tokio::main] + /// # async fn main() { + /// use zenoh_ext::AdvancedPublisherBuilderExt; + /// + /// let session = zenoh::open(zenoh::Config::default()).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() { + /// println!("Publisher has matching subscribers."); + /// } else { + /// println!("Publisher has NO MORE matching subscribers."); + /// } + /// } + /// # } + /// ``` + #[zenoh_macros::unstable] + pub fn matching_listener( + &self, + ) -> zenoh::matching::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_ext::AdvancedPublisherBuilderExt; + /// + /// let session = zenoh::open(zenoh::Config::default()).await.unwrap(); + /// let publisher = session.declare_publisher("key/expression").advanced().await.unwrap(); + /// 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), + ..self + } + } +} + +#[zenoh_macros::internal_trait] +#[zenoh_macros::unstable] +impl

SampleBuilderTrait for AdvancedPublicationBuilder<'_, P> { + #[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 { + builder: self.builder.attachment(attachment), + ..self + } + } +} + +#[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), + ..self + } + } +} + +#[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)); + } + self.builder.wait() + } +} + +#[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)); + } + self.builder.wait() + } +} + +#[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 new file mode 100644 index 0000000000..e0d9ed7e3a --- /dev/null +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -0,0 +1,1324 @@ +// +// 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::{collections::BTreeMap, future::IntoFuture, str::FromStr}; + +use zenoh::{ + config::ZenohId, + handlers::{Callback, IntoHandler}, + key_expr::KeyExpr, + liveliness::{LivelinessSubscriberBuilder, LivelinessToken}, + pubsub::SubscriberBuilder, + query::{ + ConsolidationMode, Parameters, Selector, TimeBound, TimeExpr, TimeRange, ZenohParameters, + }, + sample::{Locality, Sample, SampleKind}, + session::{EntityGlobalId, EntityId}, + 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] +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, + uhlc::ID, + zenoh::handlers::{locked, DefaultHandler}, + zenoh::internal::{runtime::ZRuntime, zlock}, + zenoh::pubsub::Subscriber, + zenoh::query::{QueryTarget, Reply, ReplyKeyExpr}, + zenoh::time::Timestamp, + zenoh::Result as ZResult, +}; + +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, + age: Option, +} + +#[zenoh_macros::unstable] +impl HistoryConfig { + /// Enable detection of late joiner publishers and query for their historical data. + /// + /// 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] + 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 + } +} + +#[derive(Default)] +/// Configure retransmission. +#[zenoh_macros::unstable] +pub struct RecoveryConfig { + periodic_queries: Option, +} + +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); + s.finish() + } +} + +#[zenoh_macros::unstable] +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. + /// 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 [`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 { + self.periodic_queries = period; + self + } +} + +/// 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, + pub(crate) key_expr: ZResult>, + pub(crate) origin: Locality, + pub(crate) retransmission: Option, + 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> { + #[zenoh_macros::unstable] + pub(crate) fn new(builder: SubscriberBuilder<'a, 'b, Handler>) -> Self { + AdvancedSubscriberBuilder { + 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), + history: None, + liveliness: false, + meta_key_expr: None, + } + } +} + +#[zenoh_macros::unstable] +impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { + /// Add callback to AdvancedSubscriber. + #[inline] + #[zenoh_macros::unstable] + pub fn callback(self, callback: F) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback> + where + F: Fn(Sample) + Send + Sync + 'static, + { + self.with(Callback::new(Arc::new(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] + #[zenoh_macros::unstable] + pub fn callback_mut( + self, + callback: F, + ) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback> + where + F: FnMut(Sample) + Send + Sync + 'static, + { + self.callback(locked(callback)) + } + + /// 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, + { + 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, + } + } +} + +#[zenoh_macros::unstable] +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] + #[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 [`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 { + self.retransmission = Some(conf); + 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 query for historical data. + /// + /// 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 { + self.history = Some(config); + self + } + + /// Allow this subscriber to be detected through liveliness. + #[zenoh_macros::unstable] + 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. + #[zenoh_macros::unstable] + pub fn subscriber_detection_metadata(mut self, meta: TryIntoKeyExpr) -> Self + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + self.meta_key_expr = Some(meta.try_into().map_err(Into::into)); + self + } + + #[zenoh_macros::unstable] + fn with_static_keys(self) -> AdvancedSubscriberBuilder<'a, 'static, '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, + history: self.history, + liveliness: self.liveliness, + meta_key_expr: self.meta_key_expr.map(|s| s.map(|s| s.into_owned())), + handler: self.handler, + } + } +} + +#[zenoh_macros::unstable] +impl Resolvable for AdvancedSubscriberBuilder<'_, '_, '_, Handler> +where + Handler: IntoHandler, + Handler::Handler: Send, +{ + 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, + 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] +struct Period { + timer: Timer, + period: Duration, +} + +#[zenoh_macros::unstable] +struct State { + next_id: usize, + global_pending_queries: u64, + sequenced_states: HashMap>, + timestamped_states: HashMap>, + session: Session, + key_expr: KeyExpr<'static>, + retransmission: bool, + period: Option, + query_target: QueryTarget, + query_timeout: Duration, + callback: Callback, + 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); + } +} + +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, + pending_queries: u64, + pending_samples: BTreeMap, +} + +/// [`AdvancedSubscriber`]. +#[zenoh_macros::unstable] +pub struct AdvancedSubscriber { + statesref: Arc>, + subscriber: Subscriber<()>, + receiver: Receiver, + _liveliness_subscriber: Option>, + _token: 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 State, sample: Sample) -> bool { + if let (Some(source_id), Some(source_sn)) = ( + sample.source_info().source_id(), + sample.source_info().source_sn(), + ) { + 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: BTreeMap::new(), + }); + if states.global_pending_queries != 0 { + 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() { + 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, + ); + 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); + } + } + } else { + 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)) { + states.callback.call(s); + last_seq_num += 1; + 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(SourceState:: { + last_delivered: None, + pending_queries: 0, + 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 { + state.last_delivered = Some(*timestamp); + states.callback.call(sample); + } else { + state.pending_samples.entry(*timestamp).or_insert(sample); + } + } + false + } else { + states.callback.call(sample); + false + } +} + +#[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: EntityGlobalId, + statesref: Arc>, +} + +#[zenoh_macros::unstable] +#[async_trait] +impl Timed for PeriodicQuery { + async fn run(&mut self) { + let mut lock = zlock!(self.statesref); + let states = &mut *lock; + 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 + / KE_AT + / &states.key_expr; + 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(); + 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(); + } + } +} + +#[zenoh_macros::unstable] +impl AdvancedSubscriber { + 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; + 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 }, + 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, + }) + }), + key_expr: key_expr.clone().into_owned(), + retransmission: retransmission.is_some(), + query_target: conf.query_target, + query_timeout: conf.query_timeout, + callback: callback.clone(), + miss_handlers: HashMap::new(), + })); + + let sub_callback = { + let statesref = statesref.clone(); + let session = conf.session.clone(); + let key_expr = key_expr.clone().into_owned(); + + move |s: Sample| { + let mut lock = zlock!(statesref); + let states = &mut *lock; + let source_id = s.source_info().source_id().cloned(); + let new = handle_sample(states, s); + + if let Some(source_id) = source_id { + if new { + spawn_periodoic_queries!(states, source_id, statesref.clone()); + } + + if let Some(state) = states.sequenced_states.get_mut(&source_id) { + if retransmission.is_some() + && state.pending_queries == 0 + && !state.pending_samples.is_empty() + { + 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 + / KE_AT + / &key_expr; + let seq_num_range = + seq_num_range(state.last_delivered.map(|s| s + 1), None); + drop(lock); + let handler = SequencedRepliesHandler { + source_id, + statesref: statesref.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 states = &mut *zlock!(handler.statesref); + handle_sample(states, s); + } + } + } + }) + .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 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()); + } + 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(( + KE_ADV_PREFIX / KE_STARSTAR / KE_AT / &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(); + } + + 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 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, + pending_queries: 0, + pending_samples: BTreeMap::new(), + }); + state.pending_queries += 1; + drop(lock); + + 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()); + } + 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({ + 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); + 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:: { + last_delivered: None, + pending_queries: 0, + pending_samples: BTreeMap::new(), + }); + state.pending_queries += 1; + drop(lock); + + 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()); + } + 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({ + 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!( + zlock!(statesref), + source_id, + statesref.clone() + ); + } + } + } else { + let mut lock = zlock!(statesref); + let states = &mut *lock; + states.global_pending_queries += 1; + drop(lock); + + let handler = InitialRepliesHandler { + statesref: statesref.clone(), + }; + let mut params = Parameters::empty(); + 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({ + 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 { + tracing::warn!( + "Received malformed liveliness token key expression: {}", + s.key_expr() + ); + } + } + } + }; + + Some( + conf + .session + .liveliness() + .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) + .wait()?, + ) + } else { + None + } + } else { + None + }; + + 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 { + Some(meta) => prefix / &meta / KE_AT, + // We need this empty chunk because af a routing matching bug + _ => prefix / KE_EMPTY / KE_AT, + }; + Some( + conf.session + .liveliness() + .declare_token(prefix / &key_expr) + .wait()?, + ) + } else { + None + }; + + let reliable_subscriber = AdvancedSubscriber { + statesref, + subscriber, + receiver, + _liveliness_subscriber: liveliness_subscriber, + _token: token, + }; + + Ok(reliable_subscriber) + } + + /// Returns the [`EntityGlobalId`] of this AdvancedSubscriber. + #[zenoh_macros::unstable] + pub fn id(&self) -> EntityGlobalId { + self.subscriber.id() + } + + /// Returns the [`KeyExpr`] this subscriber subscribes to. + #[zenoh_macros::unstable] + 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`]. + #[zenoh_macros::unstable] + 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`]. + #[zenoh_macros::unstable] + pub fn handler_mut(&mut self) -> &mut Handler { + &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 { + statesref: &self.statesref, + handler: DefaultHandler::default(), + } + } + + /// 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( + KE_ADV_PREFIX / KE_PUB / KE_STARSTAR / KE_AT / self.subscriber.key_expr(), + ) + } + + /// Undeclares this AdvancedSubscriber + #[inline] + #[zenoh_macros::unstable] + pub fn undeclare(self) -> impl Resolve> { + self.subscriber.undeclare() + } +} + +#[zenoh_macros::unstable] +#[inline] +fn flush_sequenced_source( + state: &mut SourceState, + callback: &Callback, + source_id: &EntityGlobalId, + miss_handlers: &HashMap>, +) { + if state.pending_queries == 0 && !state.pending_samples.is_empty() { + 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 => { + 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, + ); + 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); + } + _ => { + // duplicate + } + } + } + } +} + +#[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 = BTreeMap::new(); + std::mem::swap(&mut state.pending_samples, &mut pending_samples); + 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); + } + } + } +} + +#[zenoh_macros::unstable] +#[derive(Clone)] +struct InitialRepliesHandler { + statesref: Arc>, +} + +#[zenoh_macros::unstable] +impl Drop for InitialRepliesHandler { + fn drop(&mut self) { + 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() { + 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() { + flush_timestamped_source(state, &states.callback); + } + } + } +} + +#[zenoh_macros::unstable] +#[derive(Clone)] +struct SequencedRepliesHandler { + source_id: EntityGlobalId, + statesref: Arc>, +} + +#[zenoh_macros::unstable] +impl Drop for SequencedRepliesHandler { + fn drop(&mut self) { + 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 states.global_pending_queries == 0 { + flush_sequenced_source( + state, + &states.callback, + &self.source_id, + &states.miss_handlers, + ) + } + } + } +} + +#[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 states.global_pending_queries == 0 { + flush_timestamped_source(state, &self.callback); + } + } + } +} + +/// A struct that represent missed samples. +#[zenoh_macros::unstable] +pub struct Miss { + source: EntityGlobalId, + nb: u32, +} + +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, + 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 + } +} + +/// A [`Resolvable`] returned when undeclaring a [`SampleMissListener`]. +#[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 [`SampleMissListener`]. +#[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. + #[zenoh_macros::unstable] + 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/lib.rs b/zenoh-ext/src/lib.rs index 4bab50804e..2d73bd16c7 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")] @@ -30,12 +38,20 @@ pub use crate::serialization::{ ZReadIter, ZSerializer, }; #[cfg(feature = "unstable")] +#[allow(deprecated)] pub use crate::{ + advanced_cache::{CacheConfig, RepliesConfig}, + advanced_publisher::{AdvancedPublisher, AdvancedPublisherBuilder}, + advanced_subscriber::{ + AdvancedSubscriber, AdvancedSubscriberBuilder, HistoryConfig, Miss, RecoveryConfig, + SampleMissHandlerUndeclaration, SampleMissListener, SampleMissListenerBuilder, + }, publication_cache::{PublicationCache, PublicationCacheBuilder}, + publisher_ext::AdvancedPublisherBuilderExt, querying_subscriber::{ ExtractSample, FetchingSubscriber, FetchingSubscriberBuilder, KeySpace, LivelinessSpace, QueryingSubscriberBuilder, UserSpace, }, session_ext::SessionExt, - subscriber_ext::{SubscriberBuilderExt, SubscriberForward}, + subscriber_ext::{AdvancedSubscriberBuilderExt, SubscriberBuilderExt, SubscriberForward}, }; diff --git a/zenoh-ext/src/publication_cache.rs b/zenoh-ext/src/publication_cache.rs index 403a887100..bdc1d3d11e 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,8 @@ pub struct PublicationCacheBuilder<'a, 'b, 'c, const BACKGROUND: bool = false> { resources_limit: Option, } +#[allow(deprecated)] +#[zenoh_macros::unstable] impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { pub(crate) fn new( session: &'a Session, @@ -58,6 +61,8 @@ 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 TryIntoKeyExpr: TryInto>, @@ -71,29 +76,38 @@ 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. + #[zenoh_macros::unstable] + #[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. + #[zenoh_macros::unstable] + #[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. + #[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 { session: self.session, @@ -107,52 +121,73 @@ 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()) } } +/// [`PublicationCache`]. #[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct PublicationCache { local_sub: Subscriber>, _queryable: Queryable>, task: TerminatableTask, } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl PublicationCache { + #[zenoh_macros::unstable] fn new( conf: PublicationCacheBuilder<'_, '_, '_, BACKGROUND>, ) -> ZResult { @@ -297,7 +332,9 @@ impl PublicationCache { } /// Undeclare this [`PublicationCache`]`. + #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn undeclare(self) -> impl Resolve> { ResolveFuture::new(async move { let PublicationCache { @@ -313,11 +350,15 @@ 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 new file mode 100644 index 0000000000..de045d0ff0 --- /dev/null +++ b/zenoh-ext/src/publisher_ext.rs @@ -0,0 +1,73 @@ +// +// 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::{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 [`AdvancedSubscribers`](crate::AdvancedSubscriber) to recover history and/or missed samples. + #[zenoh_macros::unstable] + fn cache(self, config: CacheConfig) -> AdvancedPublisherBuilder<'a, 'b, 'c>; + + /// Allow matching [`AdvancedSubscribers`](crate::AdvancedSubscriber) to detect lost samples + /// and optionally ask for retransimission. + /// + /// 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 [`AdvancedSubscribers`](crate::AdvancedSubscriber). + /// + /// This allows [`AdvancedSubscribers`](crate::AdvancedSubscriber) 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] +impl<'a, 'b, 'c> AdvancedPublisherBuilderExt<'a, 'b, 'c> for PublisherBuilder<'a, 'b> { + /// 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 [`AdvancedSubscribers`](crate::AdvancedSubscriber) to detect lost samples + /// and optionally ask for retransimission. + /// + /// 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 [`AdvancedSubscribers`](crate::AdvancedSubscriber). + /// + /// 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() + } + + /// Turn this `Publisher` into an `AdvancedPublisher`. + #[zenoh_macros::unstable] + fn advanced(self) -> AdvancedPublisherBuilder<'a, 'b, 'c> { + AdvancedPublisherBuilder::new(self) + } +} diff --git a/zenoh-ext/src/querying_subscriber.rs b/zenoh-ext/src/querying_subscriber.rs index fa9981e2b5..9a4943756b 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,9 +57,13 @@ impl From for KeySpace { #[zenoh_macros::unstable] #[non_exhaustive] #[derive(Debug, Clone, Copy)] +#[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 } @@ -65,6 +72,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 +86,13 @@ 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( self, callback: F, @@ -99,7 +111,9 @@ 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( self, callback: F, @@ -111,7 +125,9 @@ 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( self, handler: Handler, @@ -146,10 +162,15 @@ 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 { session: self.session, @@ -166,6 +187,9 @@ impl<'a, 'b, KeySpace> QueryingSubscriberBuilder<'a, 'b, KeySpace, Callback QueryingSubscriberBuilder<'_, 'b, UserSpace, Handler, BACKGROUND> { @@ -175,13 +199,16 @@ 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 } /// 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 where IntoSelector: TryInto>, @@ -192,14 +219,18 @@ 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 { self.query_target = query_target; self } /// Change the consolidation mode to be used for queries. + #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn query_consolidation>( mut self, query_consolidation: QC, @@ -209,24 +240,33 @@ 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 { self.query_accept_replies = accept_replies; self } } +#[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 { self.query_timeout = query_timeout; self } + #[zenoh_macros::unstable] #[allow(clippy::type_complexity)] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn into_fetching_subscriber_builder( self, ) -> ZResult< @@ -285,6 +325,8 @@ impl<'a, 'b, KeySpace, Handler, const BACKGROUND: bool> } } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl Resolvable for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where Handler: IntoHandler, @@ -293,17 +335,22 @@ where type To = ZResult>; } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl Wait for QueryingSubscriberBuilder<'_, '_, KeySpace, Handler> where KeySpace: Into + Clone, 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 KeySpace: Into + Clone, @@ -313,24 +360,32 @@ 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 KeySpace: Into + Clone, @@ -338,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()) } @@ -406,6 +462,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 +484,8 @@ pub struct FetchingSubscriberBuilder< pub(crate) phantom: std::marker::PhantomData, } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl< 'a, KeySpace, @@ -437,6 +497,7 @@ impl< where TryIntoSample: ExtractSample, { + #[zenoh_macros::unstable] fn with_static_keys( self, ) -> FetchingSubscriberBuilder<'a, 'static, KeySpace, Handler, Fetch, TryIntoSample> { @@ -452,6 +513,9 @@ where } } +#[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl< 'a, 'b, @@ -463,7 +527,9 @@ where TryIntoSample: ExtractSample, { /// Add callback to [`FetchingSubscriber`]. + #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn callback( self, callback: F, @@ -482,7 +548,9 @@ 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( self, callback: F, @@ -494,7 +562,9 @@ where } /// Use the given handler to receive Samples. + #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn with( self, handler: Handler, @@ -523,6 +593,9 @@ where } } +#[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl< 'a, 'b, @@ -536,6 +609,8 @@ 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, ) -> FetchingSubscriberBuilder<'a, 'b, KeySpace, Callback, Fetch, TryIntoSample, true> @@ -552,6 +627,9 @@ where } } +#[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] +#[allow(deprecated)] impl< Handler, Fetch: FnOnce(Box) -> ZResult<()>, @@ -565,12 +643,15 @@ 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 } } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl< KeySpace, Handler, @@ -585,6 +666,8 @@ where type To = ZResult>; } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl< KeySpace, Handler, @@ -597,11 +680,14 @@ 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, Handler, @@ -617,11 +703,14 @@ 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, Fetch: FnOnce(Box) -> ZResult<()>, @@ -634,6 +723,8 @@ where type To = ZResult<()>; } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl< KeySpace, Fetch: FnOnce(Box) -> ZResult<()> + Send + Sync, @@ -644,6 +735,7 @@ where KeySpace: Into, TryIntoSample: ExtractSample + Send + Sync, { + #[zenoh_macros::unstable] fn wait(self) -> ::To { FetchingSubscriber::new(self.with_static_keys())? .subscriber @@ -652,6 +744,8 @@ where } } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl< KeySpace, Fetch: FnOnce(Box) -> ZResult<()> + Send + Sync, @@ -665,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()) } @@ -702,6 +797,7 @@ where /// # } /// ``` #[zenoh_macros::unstable] +#[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub struct FetchingSubscriber { subscriber: Subscriber<()>, callback: Callback, @@ -709,19 +805,28 @@ 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 { fn new< 'a, @@ -805,18 +910,24 @@ 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) { self.subscriber.set_background(background) } /// Return the key expression of this FetchingSubscriber + #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn key_expr(&self) -> &KeyExpr<'static> { self.subscriber.key_expr() } @@ -857,7 +968,9 @@ impl FetchingSubscriber { /// .unwrap(); /// # } /// ``` + #[zenoh_macros::unstable] #[inline] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] pub fn fetch< Fetch: FnOnce(Box) -> ZResult<()> + Send + Sync, TryIntoSample, @@ -936,6 +1049,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 +1063,8 @@ pub struct FetchBuilder< callback: Callback, } +#[zenoh_macros::unstable] +#[allow(deprecated)] impl) -> ZResult<()>, TryIntoSample> Resolvable for FetchBuilder where @@ -956,17 +1073,22 @@ 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 where @@ -975,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()) } @@ -986,6 +1109,8 @@ fn register_handler(state: Arc>, callback: Callback) - RepliesHandler { state, callback } } +#[zenoh_macros::unstable] +#[allow(deprecated)] fn run_fetch< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -1007,11 +1132,16 @@ where })) } +/// [`ExtractSample`]. #[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..8daa25e78d 100644 --- a/zenoh-ext/src/session_ext.rs +++ b/zenoh-ext/src/session_ext.rs @@ -14,12 +14,16 @@ 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.. + /// Declare a [`PublicationCache`](crate::PublicationCache). + /// /// Examples: /// ``` /// # #[tokio::main] @@ -37,6 +41,8 @@ pub trait SessionExt { /// }).await; /// # } /// ``` + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] + #[zenoh_macros::unstable] fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( &'a self, pub_key_expr: TryIntoKeyExpr, @@ -46,7 +52,9 @@ pub trait SessionExt { >>::Error: Into; } +#[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 8d931726c3..4441ebabdc 100644 --- a/zenoh-ext/src/subscriber_ext.rs +++ b/zenoh-ext/src/subscriber_ext.rs @@ -23,8 +23,10 @@ use zenoh::{ Result as ZResult, }; +#[allow(deprecated)] use crate::{ - querying_subscriber::QueryingSubscriberBuilder, ExtractSample, FetchingSubscriberBuilder, + advanced_subscriber::HistoryConfig, querying_subscriber::QueryingSubscriberBuilder, + AdvancedSubscriberBuilder, ExtractSample, FetchingSubscriberBuilder, RecoveryConfig, }; /// Allows writing `subscriber.forward(receiver)` instead of `subscriber.stream().map(Ok).forward(publisher)` @@ -37,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 { @@ -46,6 +49,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; @@ -82,6 +87,8 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { /// } /// # } /// ``` + #[zenoh_macros::unstable] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn fetching< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -120,9 +127,39 @@ pub trait SubscriberBuilderExt<'a, 'b, Handler> { /// } /// # } /// ``` + #[zenoh_macros::unstable] + #[deprecated = "Use `AdvancedPublisher` and `AdvancedSubscriber` instead."] fn querying(self) -> QueryingSubscriberBuilder<'a, 'b, Self::KeySpace, 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. + /// + /// 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 [`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>; + + /// 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; @@ -159,6 +196,7 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for SubscriberBuilde /// } /// # } /// ``` + #[zenoh_macros::unstable] fn fetching< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -208,6 +246,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, @@ -227,6 +266,43 @@ 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 [`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) + } + + /// Ask for retransmission of detected lost Samples. + /// + /// 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) + } + + /// Allow this subscriber to be detected through liveliness. + #[zenoh_macros::unstable] + fn subscriber_detection(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Handler> { + 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) + } +} + +#[zenoh_macros::unstable] +#[allow(deprecated)] impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> for LivelinessSubscriberBuilder<'a, 'b, Handler> { @@ -268,6 +344,7 @@ impl<'a, 'b, Handler> SubscriberBuilderExt<'a, 'b, Handler> /// } /// # } /// ``` + #[zenoh_macros::unstable] fn fetching< Fetch: FnOnce(Box) -> ZResult<()>, TryIntoSample, @@ -319,6 +396,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, diff --git a/zenoh-ext/tests/advanced.rs b/zenoh-ext/tests/advanced.rs new file mode 100644 index 0000000000..f7d3593c63 --- /dev/null +++ b/zenoh-ext/tests/advanced.rs @@ -0,0 +1,715 @@ +// +// 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, ModeDependentValue, WhatAmI}; +use zenoh_ext::{ + AdvancedPublisherBuilderExt, AdvancedSubscriberBuilderExt, CacheConfig, HistoryConfig, + RecoveryConfig, +}; + +#[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("error"); + + 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(); + 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()); + s + }; + + let publ = ztimeout!(peer1 + .declare_publisher(ADVANCED_HISTORY_KEYEXPR) + .cache(CacheConfig::default().max_samples(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(HistoryConfig::default())) + .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(); +} + +#[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) + .recovery(RecoveryConfig::default())) + .unwrap(); + tokio::time::sleep(SLEEP).await; + + let publ = ztimeout!(client1 + .declare_publisher(ADVANCED_RETRANSMISSION_KEYEXPR) + .cache(CacheConfig::default().max_samples(10)) + .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(); + 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) + .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) + .cache(CacheConfig::default().max_samples(10)) + .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(); + 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(); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn test_advanced_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_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 sub = ztimeout!(client2 + .declare_subscriber(ADVANCED_SAMPLE_MISS_KEYEXPR) + .advanced()) + .unwrap(); + let miss_listener = ztimeout!(sub.sample_miss_listener()).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_listener.recv_async()).unwrap(); + assert_eq!(miss.source(), publ.id()); + assert_eq!(miss.nb(), 1); + + assert!(miss_listener.try_recv().unwrap().is_none()); + + 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"; + + 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_SAMPLE_MISS_KEYEXPR) + .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 + .declare_publisher(ADVANCED_RETRANSMISSION_SAMPLE_MISS_KEYEXPR) + .cache(CacheConfig::default().max_samples(1)) + .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(); + 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_listener.recv_async()).unwrap(); + assert_eq!(miss.source(), publ.id()); + assert_eq!(miss.nb(), 2); + + assert!(miss_listener.try_recv().unwrap().is_none()); + + 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; + + 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:47455"; + + 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(); + 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()); + 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(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)) + .publisher_detection()) + .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(); +} 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; diff --git a/zenoh/Cargo.toml b/zenoh/Cargo.toml index 6a998b2962..39faaf148c 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/admin.rs b/zenoh/src/api/admin.rs index d043ec856c..28808ee664 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,39 +40,71 @@ 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") }; -); +#[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_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!("**"); + +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_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_AT, q) + })), + ); + + 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), 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_AT / 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 +114,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_AT + / 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), } @@ -105,14 +141,14 @@ pub(crate) fn on_admin_query(session: &WeakSession, query: Query) { .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); } } } @@ -153,10 +189,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/api/builders/publisher.rs b/zenoh/src/api/builders/publisher.rs index 54070ad607..f72548bfc2 100644 --- a/zenoh/src/api/builders/publisher.rs +++ b/zenoh/src/api/builders/publisher.rs @@ -288,14 +288,41 @@ 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>, + + #[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, } 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 1217a12382..eb0858fe21 100644 --- a/zenoh/src/api/publisher.rs +++ b/zenoh/src/api/publisher.rs @@ -314,6 +314,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 013536e540..cdd1f464c9 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")] @@ -557,12 +560,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 { @@ -612,6 +621,11 @@ impl WeakSession { *weak += 1; Self(session.clone()) } + + #[zenoh_macros::internal] + pub(crate) fn session(&self) -> &Session { + Session::ref_cast(&self.0) + } } impl Clone for WeakSession { @@ -2164,7 +2178,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")] @@ -2176,7 +2190,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()), @@ -2195,7 +2209,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(), 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 { diff --git a/zenoh/src/lib.rs b/zenoh/src/lib.rs index ec39dd29c3..119ee34533 100644 --- a/zenoh/src/lib.rs +++ b/zenoh/src/lib.rs @@ -81,6 +81,21 @@ 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_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")); ); @@ -172,7 +187,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 c50717fdc927da6d4b1ca121f791c9ea51c2aa9b Mon Sep 17 00:00:00 2001 From: Gabriele Baldoni Date: Wed, 11 Dec 2024 11:44:53 +0100 Subject: [PATCH 09/23] fix(serial-reopening): bug that prevented the serial link to be reused after closing a session (#1624) * fix(serial-reopening): resolved a bug that prevents sessions to be reopened over a serial link Signed-off-by: Gabriele Baldoni * fix: serial reconnections Signed-off-by: Gabriele Baldoni * feat: configurable timeout Signed-off-by: Gabriele Baldoni * deps: bump z-serial dependency Signed-off-by: Gabriele Baldoni * fix: release file on close Signed-off-by: Gabriele Baldoni * fix: closing file on error Signed-off-by: Gabriele Baldoni * fix: opening and reconnections Signed-off-by: Gabriele Baldoni * chore: updated log levels Signed-off-by: Gabriele Baldoni * fix: return on map_err Signed-off-by: Gabriele Baldoni * chore: changed warn log to debug Signed-off-by: Gabriele Baldoni * linter: make clippy happy again Signed-off-by: Gabriele Baldoni * deps: bump z-serial Signed-off-by: Gabriele Baldoni * chore: serial not as default feature Signed-off-by: Gabriele Baldoni * deps: bump z-serial Signed-off-by: Gabriele Baldoni --------- Signed-off-by: Gabriele Baldoni --- Cargo.lock | 4 +- Cargo.toml | 2 +- io/zenoh-links/zenoh-link-serial/src/lib.rs | 22 ++ .../zenoh-link-serial/src/unicast.rs | 207 +++++++++++------- zenoh/Cargo.toml | 2 +- 5 files changed, 156 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7198e5862..e49f2936ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5062,9 +5062,9 @@ checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" [[package]] name = "z-serial" -version = "0.2.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f113597c6b880587004169f14bc010e4b440981ab2ad669779d3654f9b1c4af1" +checksum = "1660dfc9f90480610f94c285a9a967b49cd2f57b3b1267d9bd7fd5d4f57c36c8" dependencies = [ "cobs", "futures", diff --git a/Cargo.toml b/Cargo.toml index b69d42f66e..c0826648ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,7 +188,7 @@ vec_map = "0.8.2" webpki-roots = "0.26.5" winapi = { version = "0.3.9", features = ["iphlpapi", "winerror"] } x509-parser = "0.16.0" -z-serial = "0.2.3" +z-serial = "0.3.1" either = "1.13.0" prost = "0.13.2" tls-listener = { version = "0.10.2", features = ["rustls-ring"] } diff --git a/io/zenoh-links/zenoh-link-serial/src/lib.rs b/io/zenoh-links/zenoh-link-serial/src/lib.rs index 04cefebd11..9d4b9012a4 100644 --- a/io/zenoh-links/zenoh-link-serial/src/lib.rs +++ b/io/zenoh-links/zenoh-link-serial/src/lib.rs @@ -38,6 +38,10 @@ const DEFAULT_BAUDRATE: u32 = 9_600; const DEFAULT_EXCLUSIVE: bool = true; +const DEFAULT_TIMEOUT: u64 = 50_000; + +const DEFAULT_RELEASE_ON_CLOSE: bool = true; + pub const SERIAL_LOCATOR_PREFIX: &str = "serial"; const SERIAL_MTU_LIMIT: BatchSize = SERIAL_MAX_MTU; @@ -94,6 +98,22 @@ pub fn get_exclusive(endpoint: &EndPoint) -> bool { } } +pub fn get_timeout(endpoint: &EndPoint) -> u64 { + if let Some(tout) = endpoint.config().get(config::TIMEOUT_RAW) { + u64::from_str(tout).unwrap_or(DEFAULT_TIMEOUT) + } else { + DEFAULT_TIMEOUT + } +} + +pub fn get_release_on_close(endpoint: &EndPoint) -> bool { + if let Some(release_on_close) = endpoint.config().get(config::RELEASE_ON_CLOSE) { + bool::from_str(release_on_close).unwrap_or(DEFAULT_RELEASE_ON_CLOSE) + } else { + DEFAULT_RELEASE_ON_CLOSE + } +} + pub fn get_unix_path_as_string(address: Address<'_>) -> String { address.as_str().to_owned() } @@ -101,4 +121,6 @@ pub fn get_unix_path_as_string(address: Address<'_>) -> String { pub mod config { pub const PORT_BAUD_RATE_RAW: &str = "baudrate"; pub const PORT_EXCLUSIVE_RAW: &str = "exclusive"; + pub const TIMEOUT_RAW: &str = "tout"; + pub const RELEASE_ON_CLOSE: &str = "release_on_close"; } diff --git a/io/zenoh-links/zenoh-link-serial/src/unicast.rs b/io/zenoh-links/zenoh-link-serial/src/unicast.rs index 3b54dcfc3a..cb68b9893c 100644 --- a/io/zenoh-links/zenoh-link-serial/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-serial/src/unicast.rs @@ -30,7 +30,7 @@ use tokio::{ }; use tokio_util::sync::CancellationToken; use z_serial::ZSerial; -use zenoh_core::{zasynclock, zasyncread, zasyncwrite}; +use zenoh_core::{bail, zasynclock, zasyncread, zasyncwrite}; use zenoh_link_commons::{ ConstructibleLinkManagerUnicast, LinkAuthId, LinkManagerUnicastTrait, LinkUnicast, LinkUnicastTrait, NewLinkChannelSender, @@ -45,7 +45,7 @@ use super::{ get_baud_rate, get_unix_path_as_string, SERIAL_ACCEPT_THROTTLE_TIME, SERIAL_DEFAULT_MTU, SERIAL_LOCATOR_PREFIX, }; -use crate::get_exclusive; +use crate::{get_exclusive, get_release_on_close, get_timeout}; struct LinkUnicastSerial { // The underlying serial port as returned by ZSerial (tokio-serial) @@ -56,13 +56,15 @@ struct LinkUnicastSerial { // already ensures that no concurrent reads or writes can happen on // the same stream: there is only one task at the time that writes on // the stream and only one task at the time that reads from the stream. - port: UnsafeCell, + port: UnsafeCell>, // The serial port path src_locator: Locator, // The serial destination path (random UUIDv4) dst_locator: Locator, // A flag that tells if the link is connected or not is_connected: Arc, + // A flag that tells if we must release the file on close. + release_on_close: bool, // Locks for reading and writing ends of the serial. write_lock: AsyncMutex<()>, read_lock: AsyncMutex<()>, @@ -73,16 +75,18 @@ unsafe impl Sync for LinkUnicastSerial {} impl LinkUnicastSerial { fn new( - port: UnsafeCell, + port: UnsafeCell>, src_path: &str, dst_path: &str, is_connected: Arc, + release_on_close: bool, ) -> Self { Self { port, src_locator: Locator::new(SERIAL_LOCATOR_PREFIX, src_path, "").unwrap(), dst_locator: Locator::new(SERIAL_LOCATOR_PREFIX, dst_path, "").unwrap(), is_connected, + release_on_close, write_lock: AsyncMutex::new(()), read_lock: AsyncMutex::new(()), } @@ -92,26 +96,31 @@ impl LinkUnicastSerial { // or concurrent writes will ever happen. The write_lock and read_lock // are respectively acquired in any read and write operation. #[allow(clippy::mut_from_ref)] - fn get_port_mut(&self) -> &mut ZSerial { - unsafe { &mut *self.port.get() } - } + fn get_port_mut(&self) -> ZResult<&mut ZSerial> { + unsafe { + let opt = &mut *self.port.get(); - fn is_ready(&self) -> bool { - let res = match self.get_port_mut().bytes_to_read() { - Ok(b) => b, - Err(e) => { - tracing::warn!( - "Unable to check if there are bytes to read in serial {}: {}", - self.src_locator, - e - ); - 0 + if let Some(port) = opt { + return Ok(port); } - }; - if res > 0 { - return true; + bail!("Serial is not opened") } - false + } + + fn clear_buffers(&self) -> ZResult<()> { + tracing::trace!("I'm cleaning the buffers"); + Ok(self + .get_port_mut()? + .clear() + .map_err(|e| zerror!("Cannot clear serial buffers: {e:?}"))?) + } + + fn set_port(&self, port: ZSerial) { + unsafe { *self.port.get() = Some(port) } + } + + fn unset_port(&self) { + unsafe { *self.port.get() = None } } } @@ -120,18 +129,18 @@ impl LinkUnicastTrait for LinkUnicastSerial { async fn close(&self) -> ZResult<()> { tracing::trace!("Closing Serial link: {}", self); let _guard = zasynclock!(self.write_lock); - self.get_port_mut().clear().map_err(|e| { - let e = zerror!("Unable to close Serial link {}: {}", self, e); - tracing::error!("{}", e); - e - })?; self.is_connected.store(false, Ordering::Release); + self.get_port_mut()?.close(); + if self.release_on_close { + self.unset_port(); + } + Ok(()) } async fn write(&self, buffer: &[u8]) -> ZResult { let _guard = zasynclock!(self.write_lock); - self.get_port_mut().write(buffer).await.map_err(|e| { + self.get_port_mut()?.write(buffer).await.map_err(|e| { let e = zerror!("Unable to write on Serial link {}: {}", self, e); tracing::error!("{}", e); e @@ -148,17 +157,14 @@ impl LinkUnicastTrait for LinkUnicastSerial { } async fn read(&self, buffer: &mut [u8]) -> ZResult { - loop { - let _guard = zasynclock!(self.read_lock); - match self.get_port_mut().read_msg(buffer).await { - Ok(read) => return Ok(read), - Err(e) => { - let e = zerror!("Read error on Serial link {}: {}", self, e); - tracing::error!("{}", e); - drop(_guard); - tokio::time::sleep(std::time::Duration::from_millis(1)).await; - continue; - } + let _guard = zasynclock!(self.read_lock); + match self.get_port_mut()?.read_msg(buffer).await { + Ok(read) => return Ok(read), + Err(e) => { + let e = zerror!("Read error on Serial link {}: {}", self, e); + tracing::error!("{}", e); + drop(_guard); + bail!("Read error on Serial link {}: {}", self, e); } } } @@ -283,8 +289,10 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastSerial { let path = get_unix_path_as_string(endpoint.address()); let baud_rate = get_baud_rate(&endpoint); let exclusive = get_exclusive(&endpoint); - tracing::trace!("Opening Serial Link on device {path:?}, with baudrate {baud_rate} and exclusive set as {exclusive}"); - let port = ZSerial::new(path.clone(), baud_rate, exclusive).map_err(|e| { + let tout = get_timeout(&endpoint); + let release_on_close = get_release_on_close(&endpoint); + tracing::trace!("Opening Serial Link on device {path:?}, with baudrate {baud_rate}, exclusive set as {exclusive} and timeout (us) {tout}"); + let mut port = ZSerial::new(path.clone(), baud_rate, exclusive).map_err(|e| { let e = zerror!( "Can not create a new Serial link bound to {:?}: {}", path, @@ -294,12 +302,17 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastSerial { e })?; + // Clear buffers + port.clear()?; + port.connect(Some(Duration::from_micros(tout))).await?; + // Create Serial link let link = Arc::new(LinkUnicastSerial::new( - UnsafeCell::new(port), + UnsafeCell::new(Some(port)), &path, &path, Arc::new(AtomicBool::new(true)), + release_on_close, )); Ok(LinkUnicast(link)) @@ -309,25 +322,17 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastSerial { let path = get_unix_path_as_string(endpoint.address()); let baud_rate = get_baud_rate(&endpoint); let exclusive = get_exclusive(&endpoint); - tracing::trace!("Creating Serial listener on device {path:?}, with baudrate {baud_rate} and exclusive set as {exclusive}"); - let port = ZSerial::new(path.clone(), baud_rate, exclusive).map_err(|e| { - let e = zerror!( - "Can not create a new Serial link bound to {:?}: {}", - path, - e - ); - tracing::warn!("{}", e); - e - })?; + let release_on_close = get_release_on_close(&endpoint); // Creating the link let is_connected = Arc::new(AtomicBool::new(false)); let dst_path = format!("{}", uuid::Uuid::new_v4()); let link = Arc::new(LinkUnicastSerial::new( - UnsafeCell::new(port), + UnsafeCell::new(None), &path, &dst_path, is_connected.clone(), + release_on_close, )); // Spawn the accept loop for the listener @@ -342,7 +347,17 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastSerial { async move { // Wait for the accept loop to terminate - let res = accept_read_task(link, token, manager, path.clone(), is_connected).await; + let res = accept_read_task( + link, + token, + manager, + path.clone(), + is_connected, + baud_rate, + exclusive, + release_on_close, + ) + .await; zasyncwrite!(listeners).remove(&path); res } @@ -390,20 +405,50 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastSerial { } } +#[allow(clippy::too_many_arguments)] async fn accept_read_task( link: Arc, token: CancellationToken, manager: NewLinkChannelSender, src_path: String, is_connected: Arc, + baud_rate: u32, + exclusive: bool, + release_on_close: bool, ) -> ZResult<()> { + #[allow(clippy::too_many_arguments)] async fn receive( link: Arc, src_path: String, is_connected: Arc, + baud_rate: u32, + exclusive: bool, + release_on_close: bool, ) -> ZResult> { - while !is_connected.load(Ordering::Acquire) && !link.is_ready() { - // Waiting to be ready, if not sleep some time. + tokio::time::sleep(Duration::from_micros(*SERIAL_ACCEPT_THROTTLE_TIME)).await; + + while is_connected.load(Ordering::Acquire) { + // The serial is already connected to nothing. + tokio::time::sleep(Duration::from_micros(*SERIAL_ACCEPT_THROTTLE_TIME)).await; + } + + tracing::trace!("Creating Serial listener on device {src_path:?}, with baudrate {baud_rate} and exclusive set as {exclusive}"); + if release_on_close { + let port = ZSerial::new(src_path.clone(), baud_rate, exclusive).map_err(|e| { + zerror!( + "Can not create a new Serial link bound to {:?}: {}", + src_path, + e + ) + })?; + + link.set_port(port); + // Cleaning RX buffer before listening + link.clear_buffers()?; + } + + while link.get_port_mut()?.accept().await.is_err() { + //Waiting to be ready, if not sleep some time. tokio::time::sleep(Duration::from_micros(*SERIAL_ACCEPT_THROTTLE_TIME)).await; } @@ -415,32 +460,40 @@ async fn accept_read_task( tracing::trace!("Ready to accept Serial connections on: {:?}", src_path); loop { - tokio::select! { - res = receive( - link.clone(), - src_path.clone(), - is_connected.clone(), - ) => { - match res { - Ok(link) => { - // Communicate the new link to the initial transport manager - if let Err(e) = manager.send_async(LinkUnicast(link.clone())).await { - tracing::error!("{}-{}: {}", file!(), line!(), e) + if !is_connected.load(Ordering::Acquire) { + tokio::select! { + res = receive( + link.clone(), + src_path.clone(), + is_connected.clone(), + baud_rate, + exclusive, + release_on_close, + ) => { + match res { + Ok(link) => { + // Communicate the new link to the initial transport manager + if let Err(e) = manager.send_async(LinkUnicast(link.clone())).await { + tracing::debug!("{}-{}: {}", file!(), line!(), e) + } + + // Ensure the creation of this link is only once + continue; } + Err(e) => { + tracing::debug!("{}. Hint: Is the serial cable connected?", e); + tokio::time::sleep(Duration::from_micros(*SERIAL_ACCEPT_THROTTLE_TIME)).await; + continue; - // Ensure the creation of this link is only once - break; - } - Err(e) => { - tracing::warn!("{}. Hint: Is the serial cable connected?", e); - tokio::time::sleep(Duration::from_micros(*SERIAL_ACCEPT_THROTTLE_TIME)).await; - continue; - + } } - } - }, + }, - _ = token.cancelled() => break, + _ = token.cancelled() => break, + } + } else { + // In this case its already connected, so we do nothing + tokio::time::sleep(Duration::from_micros(*SERIAL_ACCEPT_THROTTLE_TIME)).await; } } Ok(()) diff --git a/zenoh/Cargo.toml b/zenoh/Cargo.toml index 39faaf148c..1359bdd1b1 100644 --- a/zenoh/Cargo.toml +++ b/zenoh/Cargo.toml @@ -41,7 +41,7 @@ default = [ "transport_tls", "transport_udp", "transport_unixsock-stream", - "transport_ws", + "transport_ws" ] internal = ["zenoh-keyexpr/internal", "zenoh-config/internal"] plugins = [] From 3d9f8e1e382e718ca809bd88adf00af5df69f0ae Mon Sep 17 00:00:00 2001 From: Yuyuan Yuan Date: Wed, 11 Dec 2024 18:47:46 +0800 Subject: [PATCH 10/23] fix: add unused warning to `CloseBuilder` (#1644) --- zenoh/src/api/builders/close.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zenoh/src/api/builders/close.rs b/zenoh/src/api/builders/close.rs index 53fba6a2ab..daf53f1e27 100644 --- a/zenoh/src/api/builders/close.rs +++ b/zenoh/src/api/builders/close.rs @@ -27,6 +27,7 @@ use zenoh_runtime::ZRuntime; // NOTE: `Closeable` is only pub(crate) because it is zenoh-internal trait, so we don't // care about the `private_bounds` lint in this particular case. #[allow(private_bounds)] +#[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] pub struct CloseBuilder { closee: TCloseable::TClosee, timeout: Duration, From 49ed08ad7432286b11b31d885b12976dcf41cc9e Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Wed, 11 Dec 2024 12:22:34 +0100 Subject: [PATCH 11/23] Fix zenoh-ext Cargo.toml (#1659) --- zenoh-ext/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh-ext/Cargo.toml b/zenoh-ext/Cargo.toml index 4fdf98b8df..d433aa137c 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, features = ["default"] } +zenoh = { workspace = true, default-features = false } zenoh-macros = { workspace = true } [dev-dependencies] From 2f27e18f0b2c3d339be81f3684eb8ba70c367c5d Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Wed, 11 Dec 2024 12:24:40 +0100 Subject: [PATCH 12/23] stabilize liveliness API (#1646) * Remove unstable feature flags from liveliness examples and API * compile errors partially fixed * compilation error fixed * clippy fix * cargo doc fix * code simplified, unstable from liveliness tests removed * explicitly enable internal_config access for tests only * clippy fix, internal config global for liveliness test * compile error fix * compile fix correction * corrected shm test * cargo fmt * zenoh-ext requires internal_config * internal_config for zenohd * liveliness functionality moved from unstable * clippy fix * cargo fmt --- .github/workflows/ci.yml | 6 ++-- examples/Cargo.toml | 3 -- zenoh-ext/examples/Cargo.toml | 2 +- zenoh/Cargo.toml | 2 +- zenoh/src/api/liveliness.rs | 36 +------------------ zenoh/src/api/mod.rs | 1 - zenoh/src/api/query.rs | 1 - zenoh/src/api/session.rs | 56 ++++++++--------------------- zenoh/src/lib.rs | 3 +- zenoh/src/net/runtime/adminspace.rs | 2 +- zenoh/tests/liveliness.rs | 52 ++------------------------- zenoh/tests/matching.rs | 1 + zenoh/tests/routing.rs | 9 ----- zenoh/tests/shm.rs | 6 +++- zenohd/Cargo.toml | 1 + 15 files changed, 31 insertions(+), 150 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb5a496435..cbad34c4a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,15 +143,15 @@ jobs: uses: taiki-e/install-action@nextest - name: Run tests - run: cargo nextest run -F test --exclude zenoh-examples --exclude zenoh-plugin-example --workspace + run: cargo nextest run -F test -F internal_config --exclude zenoh-examples --exclude zenoh-plugin-example --workspace - name: Run tests with SHM if: ${{ matrix.os == 'macOS-latest' || matrix.os == 'windows-latest' }} - run: cargo nextest run -F test -F shared-memory -F unstable -E 'not (test(test_default_features))' --exclude zenoh-examples --exclude zenoh-plugin-example --workspace + run: cargo nextest run -F test -F shared-memory -F unstable -F internal_config -E 'not (test(test_default_features))' --exclude zenoh-examples --exclude zenoh-plugin-example --workspace - name: Run tests with SHM + unixpipe if: ${{ matrix.os == 'ubuntu-latest' }} - run: cargo nextest run -F test -F shared-memory -F unstable -F transport_unixpipe -E 'not (test(test_default_features))' --exclude zenoh-examples --exclude zenoh-plugin-example --workspace + run: cargo nextest run -F test -F shared-memory -F unstable -F internal_config -F transport_unixpipe -E 'not (test(test_default_features))' --exclude zenoh-examples --exclude zenoh-plugin-example --workspace - name: Check for feature leaks if: ${{ matrix.os == 'ubuntu-latest' }} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index af948d5b27..3e5283b613 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -127,17 +127,14 @@ path = "examples/z_forward.rs" [[example]] name = "z_liveliness" path = "examples/z_liveliness.rs" -required-features = ["unstable"] [[example]] name = "z_sub_liveliness" path = "examples/z_sub_liveliness.rs" -required-features = ["unstable"] [[example]] name = "z_get_liveliness" path = "examples/z_get_liveliness.rs" -required-features = ["unstable"] [[example]] name = "z_pub_thr" diff --git a/zenoh-ext/examples/Cargo.toml b/zenoh-ext/examples/Cargo.toml index 688c044d67..fc4cb22dc7 100644 --- a/zenoh-ext/examples/Cargo.toml +++ b/zenoh-ext/examples/Cargo.toml @@ -34,7 +34,7 @@ default = [] [dependencies] tokio = { workspace = true, features = ["rt", "sync", "time", "macros", "io-std"] } futures = { workspace = true } -zenoh = { workspace = true, features = ["unstable"], default-features = false } +zenoh = { workspace = true, features = ["unstable", "internal_config"], default-features = false } clap = { workspace = true, features = ["derive"] } zenoh-ext = { workspace = true, features = ["unstable"] } diff --git a/zenoh/Cargo.toml b/zenoh/Cargo.toml index 1359bdd1b1..acc6e26176 100644 --- a/zenoh/Cargo.toml +++ b/zenoh/Cargo.toml @@ -64,7 +64,7 @@ transport_udp = ["zenoh-transport/transport_udp"] transport_unixsock-stream = ["zenoh-transport/transport_unixsock-stream"] transport_ws = ["zenoh-transport/transport_ws"] transport_vsock = ["zenoh-transport/transport_vsock"] -unstable = ["internal_config", "zenoh-keyexpr/unstable", "zenoh-config/unstable"] +unstable = ["zenoh-keyexpr/unstable", "zenoh-config/unstable"] internal_config = [] [dependencies] diff --git a/zenoh/src/api/liveliness.rs b/zenoh/src/api/liveliness.rs index 3e4b0d6968..c70b67323f 100644 --- a/zenoh/src/api/liveliness.rs +++ b/zenoh/src/api/liveliness.rs @@ -93,13 +93,10 @@ use crate::{ /// } /// # } /// ``` - -#[zenoh_macros::unstable] pub struct Liveliness<'a> { pub(crate) session: &'a Session, } -#[zenoh_macros::unstable] impl<'a> Liveliness<'a> { /// Create a [`LivelinessToken`](LivelinessToken) for the given key expression. /// @@ -120,7 +117,6 @@ impl<'a> Liveliness<'a> { /// .unwrap(); /// # } /// ``` - #[zenoh_macros::unstable] pub fn declare_token<'b, TryIntoKeyExpr>( &self, key_expr: TryIntoKeyExpr, @@ -157,7 +153,6 @@ impl<'a> Liveliness<'a> { /// } /// # } /// ``` - #[zenoh_macros::unstable] pub fn declare_subscriber<'b, TryIntoKeyExpr>( &self, key_expr: TryIntoKeyExpr, @@ -194,7 +189,6 @@ impl<'a> Liveliness<'a> { /// } /// # } /// ``` - #[zenoh_macros::unstable] pub fn get<'b, TryIntoKeyExpr>( &self, key_expr: TryIntoKeyExpr, @@ -205,7 +199,7 @@ impl<'a> Liveliness<'a> { { let key_expr = key_expr.try_into().map_err(Into::into); let timeout = { - let conf = self.session.0.runtime.config().lock(); + let conf = &self.session.0.runtime.config().lock().0; Duration::from_millis(unwrap_or_default!(conf.queries_default_timeout())) }; LivelinessGetBuilder { @@ -233,19 +227,16 @@ impl<'a> Liveliness<'a> { /// # } /// ``` #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] -#[zenoh_macros::unstable] #[derive(Debug)] pub struct LivelinessTokenBuilder<'a, 'b> { pub(crate) session: &'a Session, pub(crate) key_expr: ZResult>, } -#[zenoh_macros::unstable] impl Resolvable for LivelinessTokenBuilder<'_, '_> { type To = ZResult; } -#[zenoh_macros::unstable] impl Wait for LivelinessTokenBuilder<'_, '_> { #[inline] fn wait(self) -> ::To { @@ -262,7 +253,6 @@ impl Wait for LivelinessTokenBuilder<'_, '_> { } } -#[zenoh_macros::unstable] impl IntoFuture for LivelinessTokenBuilder<'_, '_> { type Output = ::To; type IntoFuture = Ready<::To>; @@ -296,7 +286,6 @@ impl IntoFuture for LivelinessTokenBuilder<'_, '_> { /// .unwrap(); /// # } /// ``` -#[zenoh_macros::unstable] #[must_use = "Liveliness tokens will be immediately dropped and undeclared if not bound to a variable"] #[derive(Debug)] pub struct LivelinessToken { @@ -323,22 +312,18 @@ pub struct LivelinessToken { /// # } /// ``` #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] -#[zenoh_macros::unstable] pub struct LivelinessTokenUndeclaration(LivelinessToken); -#[zenoh_macros::unstable] impl Resolvable for LivelinessTokenUndeclaration { type To = ZResult<()>; } -#[zenoh_macros::unstable] impl Wait for LivelinessTokenUndeclaration { fn wait(mut self) -> ::To { self.0.undeclare_impl() } } -#[zenoh_macros::unstable] impl IntoFuture for LivelinessTokenUndeclaration { type Output = ::To; type IntoFuture = Ready<::To>; @@ -348,7 +333,6 @@ impl IntoFuture for LivelinessTokenUndeclaration { } } -#[zenoh_macros::unstable] impl LivelinessToken { /// Undeclare the [`LivelinessToken`]. /// @@ -379,7 +363,6 @@ impl LivelinessToken { } } -#[zenoh_macros::unstable] impl UndeclarableSealed<()> for LivelinessToken { type Undeclaration = LivelinessTokenUndeclaration; @@ -388,7 +371,6 @@ impl UndeclarableSealed<()> for LivelinessToken { } } -#[zenoh_macros::unstable] impl Drop for LivelinessToken { fn drop(&mut self) { if self.undeclare_on_drop { @@ -415,7 +397,6 @@ impl Drop for LivelinessToken { /// # } /// ``` #[must_use = "Resolvables do nothing unless you resolve them using `.await` or `zenoh::Wait::wait`"] -#[zenoh_macros::unstable] #[derive(Debug)] pub struct LivelinessSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = false> { pub session: &'a Session, @@ -424,7 +405,6 @@ pub struct LivelinessSubscriberBuilder<'a, 'b, Handler, const BACKGROUND: bool = pub history: bool, } -#[zenoh_macros::unstable] impl<'a, 'b> LivelinessSubscriberBuilder<'a, 'b, DefaultHandler> { /// Receive the samples for this liveliness subscription with a callback. /// @@ -443,7 +423,6 @@ impl<'a, 'b> LivelinessSubscriberBuilder<'a, 'b, DefaultHandler> { /// # } /// ``` #[inline] - #[zenoh_macros::unstable] pub fn callback(self, callback: F) -> LivelinessSubscriberBuilder<'a, 'b, Callback> where F: Fn(Sample) + Send + Sync + 'static, @@ -472,7 +451,6 @@ impl<'a, 'b> LivelinessSubscriberBuilder<'a, 'b, DefaultHandler> { /// # } /// ``` #[inline] - #[zenoh_macros::unstable] pub fn callback_mut( self, callback: F, @@ -503,7 +481,6 @@ impl<'a, 'b> LivelinessSubscriberBuilder<'a, 'b, DefaultHandler> { /// # } /// ``` #[inline] - #[zenoh_macros::unstable] pub fn with(self, handler: Handler) -> LivelinessSubscriberBuilder<'a, 'b, Handler> where Handler: IntoHandler, @@ -556,14 +533,12 @@ impl<'a, 'b> LivelinessSubscriberBuilder<'a, 'b, Callback> { impl LivelinessSubscriberBuilder<'_, '_, Handler, BACKGROUND> { #[inline] - #[zenoh_macros::unstable] pub fn history(mut self, history: bool) -> Self { self.history = history; self } } -#[zenoh_macros::unstable] impl Resolvable for LivelinessSubscriberBuilder<'_, '_, Handler> where Handler: IntoHandler + Send, @@ -572,13 +547,11 @@ where type To = ZResult>; } -#[zenoh_macros::unstable] impl Wait for LivelinessSubscriberBuilder<'_, '_, Handler> where Handler: IntoHandler + Send, Handler::Handler: Send, { - #[zenoh_macros::unstable] fn wait(self) -> ::To { use super::subscriber::SubscriberKind; @@ -606,7 +579,6 @@ where } } -#[zenoh_macros::unstable] impl IntoFuture for LivelinessSubscriberBuilder<'_, '_, Handler> where Handler: IntoHandler + Send, @@ -615,20 +587,16 @@ 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] impl Resolvable for LivelinessSubscriberBuilder<'_, '_, Callback, true> { type To = ZResult<()>; } -#[zenoh_macros::unstable] impl Wait for LivelinessSubscriberBuilder<'_, '_, Callback, true> { - #[zenoh_macros::unstable] fn wait(self) -> ::To { self.session.0.declare_liveliness_subscriber_inner( &self.key_expr?, @@ -640,12 +608,10 @@ impl Wait for LivelinessSubscriberBuilder<'_, '_, Callback, true> { } } -#[zenoh_macros::unstable] impl IntoFuture for LivelinessSubscriberBuilder<'_, '_, 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/src/api/mod.rs b/zenoh/src/api/mod.rs index 6981e02f73..e5d19325c1 100644 --- a/zenoh/src/api/mod.rs +++ b/zenoh/src/api/mod.rs @@ -22,7 +22,6 @@ pub(crate) mod encoding; pub(crate) mod handlers; pub(crate) mod info; pub(crate) mod key_expr; -#[cfg(feature = "unstable")] pub(crate) mod liveliness; #[cfg(feature = "plugins")] pub(crate) mod loader; diff --git a/zenoh/src/api/query.rs b/zenoh/src/api/query.rs index 8eff89831a..3e1f66ed12 100644 --- a/zenoh/src/api/query.rs +++ b/zenoh/src/api/query.rs @@ -146,7 +146,6 @@ impl From for Result { } } -#[cfg(feature = "unstable")] pub(crate) struct LivelinessQueryState { pub(crate) callback: Callback, } diff --git a/zenoh/src/api/session.rs b/zenoh/src/api/session.rs index cdd1f464c9..e6455cab23 100644 --- a/zenoh/src/api/session.rs +++ b/zenoh/src/api/session.rs @@ -11,10 +11,8 @@ // Contributors: // ZettaScale Zenoh Team, // -#[cfg(feature = "unstable")] -use std::collections::hash_map::Entry; use std::{ - collections::HashMap, + collections::{hash_map::Entry, HashMap}, convert::TryInto, fmt, ops::Deref, @@ -39,11 +37,7 @@ use zenoh_config::{qos::PublisherQoSConfig, unwrap_or_default, wrappers::ZenohId use zenoh_core::{zconfigurable, zread, Resolve, ResolveClosure, ResolveFuture, Wait}; use zenoh_keyexpr::keyexpr_tree::KeBoxTree; #[cfg(feature = "unstable")] -use zenoh_protocol::network::{ - declare::{DeclareToken, SubscriberId, TokenId, UndeclareToken}, - ext, - interest::InterestId, -}; +use zenoh_protocol::network::declare::SubscriberId; use zenoh_protocol::{ core::{ key_expr::{keyexpr, OwnedKeyExpr}, @@ -54,10 +48,11 @@ use zenoh_protocol::{ self, declare::{ self, common::ext::WireExprType, queryable::ext::QueryableInfoType, Declare, - DeclareBody, DeclareKeyExpr, DeclareQueryable, DeclareSubscriber, UndeclareQueryable, - UndeclareSubscriber, + DeclareBody, DeclareKeyExpr, DeclareQueryable, DeclareSubscriber, DeclareToken, + TokenId, UndeclareQueryable, UndeclareSubscriber, UndeclareToken, }, - interest::{InterestMode, InterestOptions}, + ext, + interest::{InterestId, InterestMode, InterestOptions}, push, request, AtomicRequestId, DeclareFinal, Interest, Mapping, Push, Request, RequestId, Response, ResponseFinal, }, @@ -78,10 +73,9 @@ use crate::api::selector::ZenohParameters; #[cfg(feature = "unstable")] use crate::api::{ builders::querier::QuerierBuilder, - liveliness::Liveliness, matching::{MatchingListenerState, MatchingStatus, MatchingStatusType}, querier::QuerierState, - query::{LivelinessQueryState, ReplyKeyExpr}, + query::ReplyKeyExpr, sample::SourceInfo, }; use crate::{ @@ -102,8 +96,12 @@ use crate::{ handlers::{Callback, DefaultHandler}, info::SessionInfo, key_expr::{KeyExpr, KeyExprInner}, + liveliness::Liveliness, publisher::{Priority, PublisherState}, - query::{ConsolidationMode, QueryConsolidation, QueryState, QueryTarget, Reply}, + query::{ + ConsolidationMode, LivelinessQueryState, QueryConsolidation, QueryState, QueryTarget, + Reply, + }, queryable::{Query, QueryInner, QueryableState}, sample::{DataInfo, DataInfoIntoSample, Locality, QoS, Sample, SampleKind}, selector::Selector, @@ -130,7 +128,6 @@ pub(crate) struct SessionState { pub(crate) primitives: Option>, // @TODO replace with MaybeUninit ?? pub(crate) expr_id_counter: AtomicExprId, // @TODO: manage rollover and uniqueness pub(crate) qid_counter: AtomicRequestId, - #[cfg(feature = "unstable")] pub(crate) liveliness_qid_counter: AtomicRequestId, pub(crate) local_resources: HashMap, pub(crate) remote_resources: HashMap, @@ -139,7 +136,6 @@ pub(crate) struct SessionState { pub(crate) publishers: HashMap, #[cfg(feature = "unstable")] pub(crate) queriers: HashMap, - #[cfg(feature = "unstable")] pub(crate) remote_tokens: HashMap>, //pub(crate) publications: Vec, pub(crate) subscribers: HashMap>, @@ -150,7 +146,6 @@ pub(crate) struct SessionState { #[cfg(feature = "unstable")] pub(crate) matching_listeners: HashMap>, pub(crate) queries: HashMap, - #[cfg(feature = "unstable")] pub(crate) liveliness_queries: HashMap, pub(crate) aggregated_subscribers: Vec, pub(crate) aggregated_publishers: Vec, @@ -167,7 +162,6 @@ impl SessionState { primitives: None, expr_id_counter: AtomicExprId::new(1), // Note: start at 1 because 0 is reserved for NO_RESOURCE qid_counter: AtomicRequestId::new(0), - #[cfg(feature = "unstable")] liveliness_qid_counter: AtomicRequestId::new(0), local_resources: HashMap::new(), remote_resources: HashMap::new(), @@ -176,7 +170,6 @@ impl SessionState { publishers: HashMap::new(), #[cfg(feature = "unstable")] queriers: HashMap::new(), - #[cfg(feature = "unstable")] remote_tokens: HashMap::new(), //publications: Vec::new(), subscribers: HashMap::new(), @@ -187,7 +180,6 @@ impl SessionState { #[cfg(feature = "unstable")] matching_listeners: HashMap::new(), queries: HashMap::new(), - #[cfg(feature = "unstable")] liveliness_queries: HashMap::new(), aggregated_subscribers, aggregated_publishers, @@ -1041,7 +1033,6 @@ impl Session { /// .unwrap(); /// # } /// ``` - #[zenoh_macros::unstable] pub fn liveliness(&self) -> Liveliness<'_> { Liveliness { session: self } } @@ -1614,7 +1605,6 @@ impl SessionInner { } } SubscriberKind::LivelinessSubscriber => { - #[cfg(feature = "unstable")] if kind == SubscriberKind::LivelinessSubscriber { let primitives = state.primitives()?; drop(state); @@ -1736,7 +1726,6 @@ impl SessionInner { } } - #[zenoh_macros::unstable] pub(crate) fn declare_liveliness_inner(&self, key_expr: &KeyExpr) -> ZResult { tracing::trace!("declare_liveliness({:?})", key_expr); let id = self.runtime.next_id(); @@ -1754,7 +1743,6 @@ impl SessionInner { Ok(id) } - #[cfg(feature = "unstable")] pub(crate) fn declare_liveliness_subscriber_inner( &self, key_expr: &KeyExpr, @@ -1831,7 +1819,6 @@ impl SessionInner { reliability: Reliability::Reliable, #[cfg(feature = "unstable")] source_info: SourceInfo::empty(), - #[cfg(feature = "unstable")] attachment: None, }); } @@ -1855,7 +1842,6 @@ impl SessionInner { Ok(sub_state) } - #[zenoh_macros::unstable] pub(crate) fn undeclare_liveliness(&self, tid: Id) -> ZResult<()> { let Ok(primitives) = zread!(self.state).primitives() else { return Ok(()); @@ -2276,8 +2262,6 @@ impl SessionInner { self.task_controller .spawn_with_rt(zenoh_runtime::ZRuntime::Net, { let session = WeakSession::new(self); - #[cfg(feature = "unstable")] - let zid = self.zid(); async move { tokio::select! { _ = tokio::time::sleep(timeout) => { @@ -2293,7 +2277,7 @@ impl SessionInner { query.callback.call(Reply { result: Err(ReplyError::new("Timeout", Encoding::ZENOH_STRING)), #[cfg(feature = "unstable")] - replier_id: Some(zid.into()), + replier_id: Some(session.zid().into()), }); } } @@ -2368,7 +2352,6 @@ impl SessionInner { Ok(()) } - #[cfg(feature = "unstable")] pub(crate) fn liveliness_query( self: &Arc, key_expr: &KeyExpr<'_>, @@ -2382,7 +2365,6 @@ impl SessionInner { self.task_controller .spawn_with_rt(zenoh_runtime::ZRuntime::Net, { let session = WeakSession::new(self); - let zid = self.zid(); async move { tokio::select! { _ = tokio::time::sleep(timeout) => { @@ -2393,7 +2375,7 @@ impl SessionInner { query.callback.call(Reply { result: Err(ReplyError::new("Timeout", Encoding::ZENOH_STRING)), #[cfg(feature = "unstable")] - replier_id: Some(zid.into()), + replier_id: Some(session.zid().into()), }); } } @@ -2645,9 +2627,6 @@ impl Primitives for WeakSession { } } } - #[cfg(not(feature = "unstable"))] - zenoh_protocol::network::DeclareBody::DeclareToken(_) => {} - #[cfg(feature = "unstable")] zenoh_protocol::network::DeclareBody::DeclareToken(m) => { let mut state = zwrite!(self.state); if state.primitives.is_none() { @@ -2672,7 +2651,6 @@ impl Primitives for WeakSession { reliability: Reliability::Reliable, #[cfg(feature = "unstable")] source_info: SourceInfo::empty(), - #[cfg(feature = "unstable")] attachment: None, }), #[cfg(feature = "unstable")] @@ -2695,7 +2673,6 @@ impl Primitives for WeakSession { SubscriberKind::LivelinessSubscriber, #[cfg(feature = "unstable")] Reliability::Reliable, - #[cfg(feature = "unstable")] None, ); } @@ -2707,7 +2684,6 @@ impl Primitives for WeakSession { } zenoh_protocol::network::DeclareBody::UndeclareToken(m) => { trace!("recv UndeclareToken {:?}", m.id); - #[cfg(feature = "unstable")] { let mut state = zwrite!(self.state); if state.primitives.is_none() { @@ -2729,7 +2705,6 @@ impl Primitives for WeakSession { SubscriberKind::LivelinessSubscriber, #[cfg(feature = "unstable")] Reliability::Reliable, - #[cfg(feature = "unstable")] None, ); } else if m.ext_wire_expr.wire_expr != WireExpr::empty() { @@ -2753,7 +2728,6 @@ impl Primitives for WeakSession { SubscriberKind::LivelinessSubscriber, #[cfg(feature = "unstable")] Reliability::Reliable, - #[cfg(feature = "unstable")] None, ); } @@ -2769,8 +2743,6 @@ impl Primitives for WeakSession { } DeclareBody::DeclareFinal(DeclareFinal) => { trace!("recv DeclareFinal {:?}", msg.interest_id); - - #[cfg(feature = "unstable")] if let Some(interest_id) = msg.interest_id { let mut state = zwrite!(self.state); let _ = state.liveliness_queries.remove(&interest_id); diff --git a/zenoh/src/lib.rs b/zenoh/src/lib.rs index 119ee34533..643b0e134d 100644 --- a/zenoh/src/lib.rs +++ b/zenoh/src/lib.rs @@ -386,7 +386,7 @@ pub mod scouting { /// Liveliness primitives /// /// A [`LivelinessToken`](liveliness::LivelinessToken) is a token which liveliness is tied -/// to the Zenoh [`Session`](Session) and can be monitored by remote applications. +/// to the Zenoh [`Session`] and can be monitored by remote applications. /// /// # Examples /// ### Declaring a token @@ -434,7 +434,6 @@ pub mod scouting { /// } /// # } /// ``` -#[zenoh_macros::unstable] pub mod liveliness { pub use crate::api::liveliness::{ Liveliness, LivelinessGetBuilder, LivelinessSubscriberBuilder, LivelinessToken, diff --git a/zenoh/src/net/runtime/adminspace.rs b/zenoh/src/net/runtime/adminspace.rs index 33ed04eb1e..6271bcf05a 100644 --- a/zenoh/src/net/runtime/adminspace.rs +++ b/zenoh/src/net/runtime/adminspace.rs @@ -244,7 +244,7 @@ impl AdminSpace { let requested_plugins = { let cfg_guard = admin.context.runtime.state.config.lock(); - cfg_guard.plugins().load_requests().collect::>() + cfg_guard.0.plugins().load_requests().collect::>() }; let mut diffs = Vec::new(); for plugin in active_plugins.keys() { diff --git a/zenoh/tests/liveliness.rs b/zenoh/tests/liveliness.rs index 968b243033..b88ac922bb 100644 --- a/zenoh/tests/liveliness.rs +++ b/zenoh/tests/liveliness.rs @@ -11,10 +11,10 @@ // Contributors: // ZettaScale Zenoh Team, // -#[cfg(feature = "unstable")] +#![cfg(feature = "internal_config")] + use zenoh_core::ztimeout; -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_clique() { use std::time::Duration; @@ -77,7 +77,6 @@ async fn test_liveliness_subscriber_clique() { peer2.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_query_clique() { use std::time::Duration; @@ -134,7 +133,6 @@ async fn test_liveliness_query_clique() { peer2.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_brokered() { use std::time::Duration; @@ -213,7 +211,6 @@ async fn test_liveliness_subscriber_brokered() { client2.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_query_brokered() { use std::time::Duration; @@ -284,7 +281,6 @@ async fn test_liveliness_query_brokered() { client2.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_local() { use std::time::Duration; @@ -327,7 +323,6 @@ async fn test_liveliness_subscriber_local() { peer.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_query_local() { use std::time::Duration; @@ -362,7 +357,6 @@ async fn test_liveliness_query_local() { peer.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_after_close() { use std::time::Duration; @@ -421,7 +415,6 @@ async fn test_liveliness_after_close() { /// ------------------------------------------------------- /// DOUBLE CLIENT /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_client_before() { use std::time::Duration; @@ -516,7 +509,6 @@ async fn test_liveliness_subscriber_double_client_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_client_middle() { use std::time::Duration; @@ -614,7 +606,6 @@ async fn test_liveliness_subscriber_double_client_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_client_after() { use std::time::Duration; @@ -714,7 +705,6 @@ async fn test_liveliness_subscriber_double_client_after() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_client_history_before() { use std::time::Duration; @@ -817,7 +807,6 @@ async fn test_liveliness_subscriber_double_client_history_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_client_history_middle() { use std::time::Duration; @@ -920,7 +909,6 @@ async fn test_liveliness_subscriber_double_client_history_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_client_history_after() { use std::time::Duration; @@ -1025,7 +1013,6 @@ async fn test_liveliness_subscriber_double_client_history_after() { /// ------------------------------------------------------- /// DOUBLE PEER /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_peer_before() { use std::time::Duration; @@ -1114,7 +1101,6 @@ async fn test_liveliness_subscriber_double_peer_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_peer_middle() { use std::time::Duration; @@ -1206,7 +1192,6 @@ async fn test_liveliness_subscriber_double_peer_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_peer_after() { use std::time::Duration; @@ -1300,7 +1285,6 @@ async fn test_liveliness_subscriber_double_peer_after() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_peer_history_before() { use std::time::Duration; @@ -1403,7 +1387,6 @@ async fn test_liveliness_subscriber_double_peer_history_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_peer_history_middle() { use std::time::Duration; @@ -1506,7 +1489,6 @@ async fn test_liveliness_subscriber_double_peer_history_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_peer_history_after() { use std::time::Duration; @@ -1611,7 +1593,6 @@ async fn test_liveliness_subscriber_double_peer_history_after() { /// ------------------------------------------------------- /// DOUBLE ROUTER /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_router_before() { use std::time::Duration; @@ -1711,7 +1692,6 @@ async fn test_liveliness_subscriber_double_router_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_router_middle() { use std::time::Duration; @@ -1814,7 +1794,6 @@ async fn test_liveliness_subscriber_double_router_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_router_after() { use std::time::Duration; @@ -1919,7 +1898,6 @@ async fn test_liveliness_subscriber_double_router_after() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_router_history_before() { use std::time::Duration; @@ -2027,7 +2005,6 @@ async fn test_liveliness_subscriber_double_router_history_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_router_history_middle() { use std::time::Duration; @@ -2135,7 +2112,6 @@ async fn test_liveliness_subscriber_double_router_history_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_router_history_after() { use std::time::Duration; @@ -2245,7 +2221,6 @@ async fn test_liveliness_subscriber_double_router_history_after() { /// ------------------------------------------------------- /// DOUBLE CLIENT VIA PEER /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_clientviapeer_before() { use std::time::Duration; @@ -2359,7 +2334,6 @@ async fn test_liveliness_subscriber_double_clientviapeer_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_clientviapeer_middle() { use std::time::Duration; @@ -2476,7 +2450,6 @@ async fn test_liveliness_subscriber_double_clientviapeer_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_clientviapeer_after() { use std::time::Duration; @@ -2595,7 +2568,6 @@ async fn test_liveliness_subscriber_double_clientviapeer_after() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_clientviapeer_history_before() { use std::time::Duration; @@ -2718,7 +2690,6 @@ async fn test_liveliness_subscriber_double_clientviapeer_history_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_clientviapeer_history_middle() { use std::time::Duration; @@ -2841,7 +2812,6 @@ async fn test_liveliness_subscriber_double_clientviapeer_history_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subscriber_double_clientviapeer_history_after() { use std::time::Duration; @@ -2966,7 +2936,6 @@ async fn test_liveliness_subscriber_double_clientviapeer_history_after() { /// ------------------------------------------------------- /// SUBGET CLIENT /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_client_before() { use std::time::Duration; @@ -3059,7 +3028,6 @@ async fn test_liveliness_subget_client_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_client_middle() { use std::time::Duration; @@ -3157,7 +3125,6 @@ async fn test_liveliness_subget_client_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_client_history_before() { use std::time::Duration; @@ -3254,7 +3221,6 @@ async fn test_liveliness_subget_client_history_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_client_history_middle() { use std::time::Duration; @@ -3356,7 +3322,6 @@ async fn test_liveliness_subget_client_history_middle() { /// ------------------------------------------------------- /// SUBGET PEER /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_peer_before() { use std::time::Duration; @@ -3449,7 +3414,6 @@ async fn test_liveliness_subget_peer_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_peer_middle() { use std::time::Duration; @@ -3547,7 +3511,6 @@ async fn test_liveliness_subget_peer_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_peer_history_before() { use std::time::Duration; @@ -3644,7 +3607,6 @@ async fn test_liveliness_subget_peer_history_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_peer_history_middle() { use std::time::Duration; @@ -3746,7 +3708,6 @@ async fn test_liveliness_subget_peer_history_middle() { /// ------------------------------------------------------- /// SUBGET ROUTER /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_router_before() { use std::time::Duration; @@ -3844,7 +3805,6 @@ async fn test_liveliness_subget_router_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_router_middle() { use std::time::Duration; @@ -3947,7 +3907,6 @@ async fn test_liveliness_subget_router_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_router_history_before() { use std::time::Duration; @@ -4049,7 +4008,6 @@ async fn test_liveliness_subget_router_history_before() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_subget_router_history_middle() { use std::time::Duration; @@ -4153,7 +4111,6 @@ async fn test_liveliness_subget_router_history_middle() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_regression_1() { use std::time::Duration; @@ -4237,7 +4194,6 @@ async fn test_liveliness_regression_1() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_regression_2() { use std::time::Duration; @@ -4331,7 +4287,6 @@ async fn test_liveliness_regression_2() { peer_sub.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_regression_2_history() { use std::time::Duration; @@ -4432,7 +4387,6 @@ async fn test_liveliness_regression_2_history() { peer_sub.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_regression_3() { use std::time::Duration; @@ -4538,7 +4492,6 @@ async fn test_liveliness_regression_3() { router.close().await.unwrap(); } -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_issue_1470() { // https://github.com/eclipse-zenoh/zenoh/issues/1470 @@ -4722,7 +4675,6 @@ async fn test_liveliness_issue_1470() { /// ------------------------------------------------------- /// DOUBLE UNDECLARE CLIQUE /// ------------------------------------------------------- -#[cfg(feature = "unstable")] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_liveliness_double_undeclare_clique() { use std::time::Duration; diff --git a/zenoh/tests/matching.rs b/zenoh/tests/matching.rs index 503c1f5e4b..c5cab1dff4 100644 --- a/zenoh/tests/matching.rs +++ b/zenoh/tests/matching.rs @@ -12,6 +12,7 @@ // ZettaScale Zenoh Team, // #![cfg(feature = "unstable")] +#![cfg(feature = "internal_config")] use std::time::Duration; diff --git a/zenoh/tests/routing.rs b/zenoh/tests/routing.rs index 1250e14799..83dcc2d3d9 100644 --- a/zenoh/tests/routing.rs +++ b/zenoh/tests/routing.rs @@ -31,7 +31,6 @@ use zenoh_result::bail; const TIMEOUT: Duration = Duration::from_secs(10); const MSG_COUNT: usize = 50; -#[cfg(feature = "unstable")] const LIVELINESSGET_DELAY: Duration = Duration::from_millis(10); #[derive(Debug, Clone, PartialEq, Eq)] @@ -40,13 +39,9 @@ enum Task { Sub(String, usize), Queryable(String, usize), Get(String, usize), - #[cfg(feature = "unstable")] Liveliness(String), - #[cfg(feature = "unstable")] LivelinessGet(String), - #[cfg(feature = "unstable")] LivelinessLoop(String), - #[cfg(feature = "unstable")] LivelinessSub(String), Sleep(Duration), Wait, @@ -155,7 +150,6 @@ impl Task { println!("Get got sufficient amount of messages. Done."); } - #[cfg(feature = "unstable")] // The Liveliness task. Self::Liveliness(ke) => { let _liveliness = ztimeout!(session.liveliness().declare_token(ke))?; @@ -164,7 +158,6 @@ impl Task { println!("Liveliness task done."); } - #[cfg(feature = "unstable")] // The LivelinessGet task. Self::LivelinessGet(ke) => { let mut counter = 0; @@ -192,7 +185,6 @@ impl Task { } // The LivelinessLoop task. - #[cfg(feature = "unstable")] Self::LivelinessLoop(ke) => { let mut liveliness: Option = None; @@ -221,7 +213,6 @@ impl Task { println!("LivelinessLoop task done."); } - #[cfg(feature = "unstable")] // The LivelinessSub task. Self::LivelinessSub(ke) => { let sub = ztimeout!(session.liveliness().declare_subscriber(ke))?; diff --git a/zenoh/tests/shm.rs b/zenoh/tests/shm.rs index 327ad76c0e..50194e7662 100644 --- a/zenoh/tests/shm.rs +++ b/zenoh/tests/shm.rs @@ -11,7 +11,11 @@ // Contributors: // ZettaScale Zenoh Team, // -#![cfg(all(feature = "unstable", feature = "shared-memory"))] +#![cfg(all( + feature = "unstable", + feature = "shared-memory", + feature = "internal_config" +))] use std::{ sync::{ atomic::{AtomicUsize, Ordering}, diff --git a/zenohd/Cargo.toml b/zenohd/Cargo.toml index a422b0e6c7..7a75ae6c36 100644 --- a/zenohd/Cargo.toml +++ b/zenohd/Cargo.toml @@ -44,6 +44,7 @@ zenoh = { workspace = true, features = [ "internal", "plugins", "runtime_plugins", + "internal_config", ] } zenoh-config = { workspace = true } From 83bc4edb56a76f98d4f692db12c458a13bfcaf16 Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Wed, 11 Dec 2024 13:38:44 +0100 Subject: [PATCH 13/23] feat: add background to advanced subscriber (#1651) * Expose and use ke macro * Fix SourceInfo publication * Add AdvancedPublisher AdvancedSubscriber and AdvancedSubscriber * Fix doctests * Fix doc warnings * Remove debug trace * Add history test * Fix periodic queries * Remove debug trace * Lower test debug level * Add retransmission tests * Liveliness sub callback shoud increase pending queries counter * Liveliness sub callback shoud spawn periodic queries when enbaled * Add late_joiner test * Only treat pending samples when there are no more pending queries * Apply proper sequencing for history * Improve AdvancedSubscriber * Code reorg * Code reorg * Fix deduplication * Subscribe to liveliness tokens with history * Update builders * Add examples * Fix rustdoc * Move stuff in State * Code reorg * Add smaple_miss_callback * Add sample miss test * Update z_advanced_sub example * Explicit use in examples * Update API * Fix rustdoc * Allow sample miss detection when recovery disabled * Add miss_sample_callback to DataSubscriberBuilderExt * Add sample_miss_detection to PublisherBuilderExt * Add test_advanced_sample_miss test * Deliver sample even when no miss callback * Replace sample_miss_callback with sample_miss_listener * Fix clippy warnings * Fix tests * Add HistoryConf max_samples option * Add HistoryConf max_age option * Use BTreeMap * Add meta_keyexpr option * Add late_joiner_detection and meta_keyexpr options on Subcriber side * Renaming * Fix compilation issues * Remove AdvancedCache from public API * Update Session admin to match AdvancedSub * Gather constants * Fix doc build * Renaming * Mark PublicationCache and QueryingSubscriber as deprecated and remove related examples * Remove z_pub_cache and z_query_sub entries from zenoh-ext examples README * Add z_advanced_pub and z_advanced_sub to zenoh-ext examples Cargo.toml * Add CacheConfig replies_qos option * Call cache directly from publisher * Update doc * Add missing unstable tags * Add missing unstable tags * Add missing unstable tags * Add unstable tag everywhere * Add missing AdvancedSubscriber methods * Fix WeakSession::Session internal function * Expose missing SampleMissListener and related structs * Add AdvancedPublisherBuilderExt::advanced function * Add missing AdvancedPublisherBuilder functions * Fix doctests * Expose Miss struct * impl QoSBuilderTrait for AdvancedPublisherBuilder * Propagate PublisherBuilder values to AdvancedPublisherBuilder * Rename AdvancedSubscriber::close() * Add unstable tags * Add AdvancedSubscriber::detect_publishers function * Remove debug println * Renaming * Add unstable tags * Use std Range * Spawn Timer in a tokio runtime * Fix panic when last_delivered is None * Release lock before calling get * Update key mapping * Improve doc * fix: fix callback API (#1647) * Update doc * feat: add background to advanced subscriber * Fix ke_liveliness * fix: add missing mut * Fix doc * Fix doc * Fix zenoh-ext Cargo.toml --------- Co-authored-by: OlivierHecart --- zenoh-ext/src/advanced_subscriber.rs | 82 ++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/zenoh-ext/src/advanced_subscriber.rs b/zenoh-ext/src/advanced_subscriber.rs index e0d9ed7e3a..e922c4ecb2 100644 --- a/zenoh-ext/src/advanced_subscriber.rs +++ b/zenoh-ext/src/advanced_subscriber.rs @@ -189,7 +189,7 @@ impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { { AdvancedSubscriberBuilder { session: self.session, - key_expr: self.key_expr.map(|s| s.into_owned()), + key_expr: self.key_expr, origin: self.origin, retransmission: self.retransmission, query_target: self.query_target, @@ -203,7 +203,30 @@ impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, DefaultHandler> { } #[zenoh_macros::unstable] -impl<'a, 'c, Handler> AdvancedSubscriberBuilder<'a, '_, 'c, Handler> { +impl<'a, 'b, 'c> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback> { + /// Register the subscriber callback to be run in background until the session is closed. + /// + /// Background builder doesn't return a `AdvancedSubscriber` object anymore. + pub fn background(self) -> AdvancedSubscriberBuilder<'a, 'b, 'c, Callback, true> { + AdvancedSubscriberBuilder { + session: self.session, + key_expr: self.key_expr, + 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: self.handler, + } + } +} + +#[zenoh_macros::unstable] +impl<'a, 'c, Handler, const BACKGROUND: bool> + AdvancedSubscriberBuilder<'a, '_, 'c, Handler, BACKGROUND> +{ /// 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] @@ -323,6 +346,35 @@ where } } +#[zenoh_macros::unstable] +impl Resolvable for AdvancedSubscriberBuilder<'_, '_, '_, Callback, true> { + type To = ZResult<()>; +} + +#[zenoh_macros::unstable] +impl Wait for AdvancedSubscriberBuilder<'_, '_, '_, Callback, true> { + #[zenoh_macros::unstable] + fn wait(self) -> ::To { + let mut sub = AdvancedSubscriber::new(self.with_static_keys())?; + sub.subscriber.set_background(true); + if let Some(mut liveliness_sub) = sub.liveliness_subscriber.take() { + liveliness_sub.set_background(true); + } + Ok(()) + } +} + +#[zenoh_macros::unstable] +impl IntoFuture for AdvancedSubscriberBuilder<'_, '_, '_, Callback, true> { + 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, @@ -343,6 +395,7 @@ struct State { query_timeout: Duration, callback: Callback, miss_handlers: HashMap>, + token: Option, } #[zenoh_macros::unstable] @@ -387,8 +440,7 @@ pub struct AdvancedSubscriber { statesref: Arc>, subscriber: Subscriber<()>, receiver: Receiver, - _liveliness_subscriber: Option>, - _token: Option, + liveliness_subscriber: Option>, } #[zenoh_macros::unstable] @@ -573,6 +625,7 @@ impl AdvancedSubscriber { query_timeout: conf.query_timeout, callback: callback.clone(), miss_handlers: HashMap::new(), + token: None, })); let sub_callback = { @@ -868,7 +921,7 @@ impl AdvancedSubscriber { None }; - let token = if conf.liveliness { + if conf.liveliness { let prefix = KE_ADV_PREFIX / KE_SUB / &subscriber.id().zid().into_keyexpr() @@ -878,22 +931,19 @@ impl AdvancedSubscriber { // We need this empty chunk because af a routing matching bug _ => prefix / KE_EMPTY / KE_AT, }; - Some( - conf.session - .liveliness() - .declare_token(prefix / &key_expr) - .wait()?, - ) - } else { - None - }; + let token = conf + .session + .liveliness() + .declare_token(prefix / &key_expr) + .wait()?; + zlock!(statesref).token = Some(token) + } let reliable_subscriber = AdvancedSubscriber { statesref, subscriber, receiver, - _liveliness_subscriber: liveliness_subscriber, - _token: token, + liveliness_subscriber, }; Ok(reliable_subscriber) From 911382df034de6b23d0f47373feaec6e7b9b2af5 Mon Sep 17 00:00:00 2001 From: Oussama Teffahi <70609372+oteffahi@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:29:37 +0100 Subject: [PATCH 14/23] Add interface binding to QUIC link (#1652) * Add interface binding for QUIC listener * Add interface binding for QUIC link * Add TLS and QUIC interface binding tests * Fix incorrect port number in test --- Cargo.lock | 1 + io/zenoh-links/zenoh-link-quic/Cargo.toml | 1 + io/zenoh-links/zenoh-link-quic/src/unicast.rs | 59 ++- io/zenoh-links/zenoh-link-quic/src/utils.rs | 20 +- io/zenoh-transport/tests/unicast_openclose.rs | 360 +++++++++++------- 5 files changed, 280 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e49f2936ea..3c2fa5212e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5331,6 +5331,7 @@ dependencies = [ "zenoh-link-commons", "zenoh-protocol", "zenoh-result", + "zenoh-util", ] [[package]] diff --git a/io/zenoh-links/zenoh-link-quic/Cargo.toml b/io/zenoh-links/zenoh-link-quic/Cargo.toml index 6132abeaca..4cd8cee7f5 100644 --- a/io/zenoh-links/zenoh-link-quic/Cargo.toml +++ b/io/zenoh-links/zenoh-link-quic/Cargo.toml @@ -51,6 +51,7 @@ zenoh-core = { workspace = true } zenoh-link-commons = { workspace = true, features = ["tls"] } zenoh-protocol = { workspace = true } zenoh-result = { workspace = true } +zenoh-util = { workspace = true } [package.metadata.cargo-machete] ignored = ["rustls-webpki"] \ No newline at end of file diff --git a/io/zenoh-links/zenoh-link-quic/src/unicast.rs b/io/zenoh-links/zenoh-link-quic/src/unicast.rs index 3618a7a625..4f69a3ae1e 100644 --- a/io/zenoh-links/zenoh-link-quic/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-quic/src/unicast.rs @@ -20,7 +20,10 @@ use std::{ }; use async_trait::async_trait; -use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig}; +use quinn::{ + crypto::rustls::{QuicClientConfig, QuicServerConfig}, + EndpointConfig, +}; use time::OffsetDateTime; use tokio::sync::Mutex as AsyncMutex; use tokio_util::sync::CancellationToken; @@ -270,8 +273,30 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastQuic { } else { Ipv6Addr::UNSPECIFIED.into() }; - let mut quic_endpoint = quinn::Endpoint::client(SocketAddr::new(ip_addr, 0)) - .map_err(|e| zerror!("Can not create a new QUIC link bound to {}: {}", host, e))?; + + // Initialize the Endpoint + let mut quic_endpoint = if let Some(iface) = client_crypto.bind_iface { + async { + // Bind the UDP socket + let socket = tokio::net::UdpSocket::bind(SocketAddr::new(ip_addr, 0)).await?; + zenoh_util::net::set_bind_to_device_udp_socket(&socket, iface)?; + + // create the Endpoint with this socket + let runtime = quinn::default_runtime().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::Other, "no async runtime found") + })?; + ZResult::Ok(quinn::Endpoint::new_with_abstract_socket( + EndpointConfig::default(), + None, + runtime.wrap_udp_socket(socket.into_std()?)?, + runtime, + )?) + } + .await + } else { + quinn::Endpoint::client(SocketAddr::new(ip_addr, 0)).map_err(Into::into) + } + .map_err(|e| zerror!("Can not create a new QUIC link bound to {host}: {e}"))?; let quic_config: QuicClientConfig = client_crypto .client_config @@ -324,7 +349,7 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastQuic { Ok(LinkUnicast(link)) } - async fn new_listener(&self, mut endpoint: EndPoint) -> ZResult { + async fn new_listener(&self, endpoint: EndPoint) -> ZResult { let epaddr = endpoint.address(); let epconf = endpoint.config(); @@ -367,15 +392,35 @@ impl LinkManagerUnicastTrait for LinkManagerUnicastQuic { .max_concurrent_bidi_streams(1_u8.into()); // Initialize the Endpoint - let quic_endpoint = quinn::Endpoint::server(server_config, addr) - .map_err(|e| zerror!("Can not create a new QUIC listener on {}: {}", addr, e))?; + let quic_endpoint = if let Some(iface) = server_crypto.bind_iface { + async { + // Bind the UDP socket + let socket = tokio::net::UdpSocket::bind(addr).await?; + zenoh_util::net::set_bind_to_device_udp_socket(&socket, iface)?; + + // create the Endpoint with this socket + let runtime = quinn::default_runtime().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::Other, "no async runtime found") + })?; + ZResult::Ok(quinn::Endpoint::new_with_abstract_socket( + EndpointConfig::default(), + Some(server_config), + runtime.wrap_udp_socket(socket.into_std()?)?, + runtime, + )?) + } + .await + } else { + quinn::Endpoint::server(server_config, addr).map_err(Into::into) + } + .map_err(|e| zerror!("Can not create a new QUIC listener on {}: {}", addr, e))?; let local_addr = quic_endpoint .local_addr() .map_err(|e| zerror!("Can not create a new QUIC listener on {}: {}", addr, e))?; // Update the endpoint locator address - endpoint = EndPoint::new( + let endpoint = EndPoint::new( endpoint.protocol(), local_addr.to_string(), endpoint.metadata(), diff --git a/io/zenoh-links/zenoh-link-quic/src/utils.rs b/io/zenoh-links/zenoh-link-quic/src/utils.rs index c00fb8e3de..e979f2a9cf 100644 --- a/io/zenoh-links/zenoh-link-quic/src/utils.rs +++ b/io/zenoh-links/zenoh-link-quic/src/utils.rs @@ -28,7 +28,9 @@ use rustls::{ use secrecy::ExposeSecret; use webpki::anchor_from_trusted_cert; use zenoh_config::Config as ZenohConfig; -use zenoh_link_commons::{tls::WebPkiVerifierAnyServerName, ConfigurationInspector}; +use zenoh_link_commons::{ + tls::WebPkiVerifierAnyServerName, ConfigurationInspector, BIND_INTERFACE, +}; use zenoh_protocol::core::{ endpoint::{Address, Config}, parameters, @@ -151,13 +153,14 @@ impl ConfigurationInspector for TlsConfigurator { } } -pub(crate) struct TlsServerConfig { +pub(crate) struct TlsServerConfig<'a> { pub(crate) server_config: ServerConfig, pub(crate) tls_close_link_on_expiration: bool, + pub(crate) bind_iface: Option<&'a str>, } -impl TlsServerConfig { - pub async fn new(config: &Config<'_>) -> ZResult { +impl<'a> TlsServerConfig<'a> { + pub async fn new(config: &'a Config<'_>) -> ZResult { let tls_server_client_auth: bool = match config.get(TLS_ENABLE_MTLS) { Some(s) => s .parse() @@ -231,6 +234,7 @@ impl TlsServerConfig { Ok(TlsServerConfig { server_config: sc, tls_close_link_on_expiration, + bind_iface: config.get(BIND_INTERFACE), }) } @@ -255,13 +259,14 @@ impl TlsServerConfig { } } -pub(crate) struct TlsClientConfig { +pub(crate) struct TlsClientConfig<'a> { pub(crate) client_config: ClientConfig, pub(crate) tls_close_link_on_expiration: bool, + pub(crate) bind_iface: Option<&'a str>, } -impl TlsClientConfig { - pub async fn new(config: &Config<'_>) -> ZResult { +impl<'a> TlsClientConfig<'a> { + pub async fn new(config: &'a Config<'_>) -> ZResult { let tls_client_server_auth: bool = match config.get(TLS_ENABLE_MTLS) { Some(s) => s .parse() @@ -375,6 +380,7 @@ impl TlsClientConfig { Ok(TlsClientConfig { client_config: cc, tls_close_link_on_expiration, + bind_iface: config.get(BIND_INTERFACE), }) } diff --git a/io/zenoh-transport/tests/unicast_openclose.rs b/io/zenoh-transport/tests/unicast_openclose.rs index f2300a2a8e..4aaec3d4a5 100644 --- a/io/zenoh-transport/tests/unicast_openclose.rs +++ b/io/zenoh-transport/tests/unicast_openclose.rs @@ -562,80 +562,8 @@ async fn openclose_tls_only() { use zenoh_link::tls::config::*; zenoh_util::init_log_from_env_or("error"); - // NOTE: this an auto-generated pair of certificate and key. - // The target domain is localhost, so it has no real - // mapping to any existing domain. The certificate and key - // have been generated using: https://github.com/jsha/minica - let key = "-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAsfqAuhElN4HnyeqLovSd4Qe+nNv5AwCjSO+HFiF30x3vQ1Hi -qRA0UmyFlSqBnFH3TUHm4Jcad40QfrX8f11NKGZdpvKHsMYqYjZnYkRFGS2s4fQy -aDbV5M06s3UDX8ETPgY41Y8fCKTSVdi9iHkwcVrXMxUu4IBBx0C1r2GSo3gkIBnU -cELdFdaUOSbdCipJhbnkwixEr2h7PXxwba7SIZgZtRaQWak1VE9b716qe3iMuMha -Efo/UoFmeZCPu5spfwaOZsnCsxRPk2IjbzlsHTJ09lM9wmbEFHBMVAXejLTk++Sr -Xt8jASZhNen/2GzyLQNAquGn98lCMQ6SsE9vLQIDAQABAoIBAGQkKggHm6Q20L+4 -2+bNsoOqguLplpvM4RMpyx11qWE9h6GeUmWD+5yg+SysJQ9aw0ZSHWEjRD4ePji9 -lxvm2IIxzuIftp+NcM2gBN2ywhpfq9XbO/2NVR6PJ0dQQJzBG12bzKDFDdYkP0EU -WdiPL+WoEkvo0F57bAd77n6G7SZSgxYekBF+5S6rjbu5I1cEKW+r2vLehD4uFCVX -Q0Tu7TyIOE1KJ2anRb7ZXVUaguNj0/Er7EDT1+wN8KJKvQ1tYGIq/UUBtkP9nkOI -9XJd25k6m5AQPDddzd4W6/5+M7kjyVPi3CsQcpBPss6ueyecZOMaKqdWAHeEyaak -r67TofUCgYEA6GBa+YkRvp0Ept8cd5mh4gCRM8wUuhtzTQnhubCPivy/QqMWScdn -qD0OiARLAsqeoIfkAVgyqebVnxwTrKTvWe0JwpGylEVWQtpGz3oHgjST47yZxIiY -CSAaimi2CYnJZ+QB2oBkFVwNCuXdPEGX6LgnOGva19UKrm6ONsy6V9MCgYEAxBJu -fu4dGXZreARKEHa/7SQjI9ayAFuACFlON/EgSlICzQyG/pumv1FsMEiFrv6w7PRj -4AGqzyzGKXWVDRMrUNVeGPSKJSmlPGNqXfPaXRpVEeB7UQhAs5wyMrWDl8jEW7Ih -XcWhMLn1f/NOAKyrSDSEaEM+Nuu+xTifoAghvP8CgYEAlta9Fw+nihDIjT10cBo0 -38w4dOP7bFcXQCGy+WMnujOYPzw34opiue1wOlB3FIfL8i5jjY/fyzPA5PhHuSCT -Ec9xL3B9+AsOFHU108XFi/pvKTwqoE1+SyYgtEmGKKjdKOfzYA9JaCgJe1J8inmV -jwXCx7gTJVjwBwxSmjXIm+sCgYBQF8NhQD1M0G3YCdCDZy7BXRippCL0OGxVfL2R -5oKtOVEBl9NxH/3+evE5y/Yn5Mw7Dx3ZPHUcygpslyZ6v9Da5T3Z7dKcmaVwxJ+H -n3wcugv0EIHvOPLNK8npovINR6rGVj6BAqD0uZHKYYYEioQxK5rGyGkaoDQ+dgHm -qku12wKBgQDem5FvNp5iW7mufkPZMqf3sEGtu612QeqejIPFM1z7VkUgetsgPBXD -tYsqC2FtWzY51VOEKNpnfH7zH5n+bjoI9nAEAW63TK9ZKkr2hRGsDhJdGzmLfQ7v -F6/CuIw9EsAq6qIB8O88FXQqald+BZOx6AzB8Oedsz/WtMmIEmr/+Q== ------END RSA PRIVATE KEY-----"; - - let cert = "-----BEGIN CERTIFICATE----- -MIIDLjCCAhagAwIBAgIIeUtmIdFQznMwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgMDc4ZGE3MCAXDTIzMDMwNjE2MDMxOFoYDzIxMjMw -MzA2MTYwMzE4WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCx+oC6ESU3gefJ6oui9J3hB76c2/kDAKNI74cWIXfT -He9DUeKpEDRSbIWVKoGcUfdNQebglxp3jRB+tfx/XU0oZl2m8oewxipiNmdiREUZ -Lazh9DJoNtXkzTqzdQNfwRM+BjjVjx8IpNJV2L2IeTBxWtczFS7ggEHHQLWvYZKj -eCQgGdRwQt0V1pQ5Jt0KKkmFueTCLESvaHs9fHBtrtIhmBm1FpBZqTVUT1vvXqp7 -eIy4yFoR+j9SgWZ5kI+7myl/Bo5mycKzFE+TYiNvOWwdMnT2Uz3CZsQUcExUBd6M -tOT75Kte3yMBJmE16f/YbPItA0Cq4af3yUIxDpKwT28tAgMBAAGjdjB0MA4GA1Ud -DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T -AQH/BAIwADAfBgNVHSMEGDAWgBTWfAmQ/BUIQm/9/llJJs2jUMWzGzAUBgNVHREE -DTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAG/POnBob0S7iYwsbtI2 -3LTTbRnmseIErtJuJmI9yYzgVIm6sUSKhlIUfAIm4rfRuzE94KFeWR2w9RabxOJD -wjYLLKvQ6rFY5g2AV/J0TwDjYuq0absdaDPZ8MKJ+/lpGYK3Te+CTOfq5FJRFt1q -GOkXAxnNpGg0obeRWRKFiAMHbcw6a8LIMfRjCooo3+uSQGsbVzGxSB4CYo720KcC -9vB1K9XALwzoqCewP4aiQsMY1GWpAmzXJftY3w+lka0e9dBYcdEdOqxSoZb5OBBZ -p5e60QweRuJsb60aUaCG8HoICevXYK2fFqCQdlb5sIqQqXyN2K6HuKAFywsjsGyJ -abY= ------END CERTIFICATE-----"; - // Configure the client - let ca = "-----BEGIN CERTIFICATE----- -MIIDSzCCAjOgAwIBAgIIB42n1ZIkOakwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgMDc4ZGE3MCAXDTIzMDMwNjE2MDMwN1oYDzIxMjMw -MzA2MTYwMzA3WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAwNzhkYTcwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIuCq24O4P4Aep5vAVlrIQ7P8+ -uWWgcHIFYa02TmhBUB/hjo0JANCQvAtpVNuQ8NyKPlqnnq1cttePbSYVeA0rrnOs -DcfySAiyGBEY9zMjFfHJtH1wtrPcJEU8XIEY3xUlrAJE2CEuV9dVYgfEEydnvgLc -8Ug0WXSiARjqbnMW3l8jh6bYCp/UpL/gSM4mxdKrgpfyPoweGhlOWXc3RTS7cqM9 -T25acURGOSI6/g8GF0sNE4VZmUvHggSTmsbLeXMJzxDWO+xVehRmbQx3IkG7u++b -QdRwGIJcDNn7zHlDMHtQ0Z1DBV94fZNBwCULhCBB5g20XTGw//S7Fj2FPwyhAgMB -AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTWfAmQ/BUIQm/9 -/llJJs2jUMWzGzAfBgNVHSMEGDAWgBTWfAmQ/BUIQm/9/llJJs2jUMWzGzANBgkq -hkiG9w0BAQsFAAOCAQEAvtcZFAELKiTuOiAeYts6zeKxc+nnHCzayDeD/BDCbxGJ -e1n+xdHjLtWGd+/Anc+fvftSYBPTFQqCi84lPiUIln5z/rUxE+ke81hNPIfw2obc -yIg87xCabQpVyEh8s+MV+7YPQ1+fH4FuSi2Fck1FejxkVqN2uOZPvOYUmSTsaVr1 -8SfRnwJNZ9UMRPM2bD4Jkvj0VcL42JM3QkOClOzYW4j/vll2cSs4kx7er27cIoo1 -Ck0v2xSPAiVjg6w65rUQeW6uB5m0T2wyj+wm0At8vzhZPlgS1fKhcmT2dzOq3+oN -R+IdLiXcyIkg0m9N8I17p0ljCSkbrgGMD3bbePRTfg== ------END CERTIFICATE-----"; + let (ca, cert, key) = get_tls_certs(); let mut endpoint: EndPoint = format!("tls/localhost:{}", 13030).parse().unwrap(); endpoint @@ -659,80 +587,7 @@ R+IdLiXcyIkg0m9N8I17p0ljCSkbrgGMD3bbePRTfg== async fn openclose_quic_only() { use zenoh_link::quic::config::*; - // NOTE: this an auto-generated pair of certificate and key. - // The target domain is localhost, so it has no real - // mapping to any existing domain. The certificate and key - // have been generated using: https://github.com/jsha/minica - let key = "-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAsfqAuhElN4HnyeqLovSd4Qe+nNv5AwCjSO+HFiF30x3vQ1Hi -qRA0UmyFlSqBnFH3TUHm4Jcad40QfrX8f11NKGZdpvKHsMYqYjZnYkRFGS2s4fQy -aDbV5M06s3UDX8ETPgY41Y8fCKTSVdi9iHkwcVrXMxUu4IBBx0C1r2GSo3gkIBnU -cELdFdaUOSbdCipJhbnkwixEr2h7PXxwba7SIZgZtRaQWak1VE9b716qe3iMuMha -Efo/UoFmeZCPu5spfwaOZsnCsxRPk2IjbzlsHTJ09lM9wmbEFHBMVAXejLTk++Sr -Xt8jASZhNen/2GzyLQNAquGn98lCMQ6SsE9vLQIDAQABAoIBAGQkKggHm6Q20L+4 -2+bNsoOqguLplpvM4RMpyx11qWE9h6GeUmWD+5yg+SysJQ9aw0ZSHWEjRD4ePji9 -lxvm2IIxzuIftp+NcM2gBN2ywhpfq9XbO/2NVR6PJ0dQQJzBG12bzKDFDdYkP0EU -WdiPL+WoEkvo0F57bAd77n6G7SZSgxYekBF+5S6rjbu5I1cEKW+r2vLehD4uFCVX -Q0Tu7TyIOE1KJ2anRb7ZXVUaguNj0/Er7EDT1+wN8KJKvQ1tYGIq/UUBtkP9nkOI -9XJd25k6m5AQPDddzd4W6/5+M7kjyVPi3CsQcpBPss6ueyecZOMaKqdWAHeEyaak -r67TofUCgYEA6GBa+YkRvp0Ept8cd5mh4gCRM8wUuhtzTQnhubCPivy/QqMWScdn -qD0OiARLAsqeoIfkAVgyqebVnxwTrKTvWe0JwpGylEVWQtpGz3oHgjST47yZxIiY -CSAaimi2CYnJZ+QB2oBkFVwNCuXdPEGX6LgnOGva19UKrm6ONsy6V9MCgYEAxBJu -fu4dGXZreARKEHa/7SQjI9ayAFuACFlON/EgSlICzQyG/pumv1FsMEiFrv6w7PRj -4AGqzyzGKXWVDRMrUNVeGPSKJSmlPGNqXfPaXRpVEeB7UQhAs5wyMrWDl8jEW7Ih -XcWhMLn1f/NOAKyrSDSEaEM+Nuu+xTifoAghvP8CgYEAlta9Fw+nihDIjT10cBo0 -38w4dOP7bFcXQCGy+WMnujOYPzw34opiue1wOlB3FIfL8i5jjY/fyzPA5PhHuSCT -Ec9xL3B9+AsOFHU108XFi/pvKTwqoE1+SyYgtEmGKKjdKOfzYA9JaCgJe1J8inmV -jwXCx7gTJVjwBwxSmjXIm+sCgYBQF8NhQD1M0G3YCdCDZy7BXRippCL0OGxVfL2R -5oKtOVEBl9NxH/3+evE5y/Yn5Mw7Dx3ZPHUcygpslyZ6v9Da5T3Z7dKcmaVwxJ+H -n3wcugv0EIHvOPLNK8npovINR6rGVj6BAqD0uZHKYYYEioQxK5rGyGkaoDQ+dgHm -qku12wKBgQDem5FvNp5iW7mufkPZMqf3sEGtu612QeqejIPFM1z7VkUgetsgPBXD -tYsqC2FtWzY51VOEKNpnfH7zH5n+bjoI9nAEAW63TK9ZKkr2hRGsDhJdGzmLfQ7v -F6/CuIw9EsAq6qIB8O88FXQqald+BZOx6AzB8Oedsz/WtMmIEmr/+Q== ------END RSA PRIVATE KEY-----"; - - let cert = "-----BEGIN CERTIFICATE----- -MIIDLjCCAhagAwIBAgIIeUtmIdFQznMwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgMDc4ZGE3MCAXDTIzMDMwNjE2MDMxOFoYDzIxMjMw -MzA2MTYwMzE4WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCx+oC6ESU3gefJ6oui9J3hB76c2/kDAKNI74cWIXfT -He9DUeKpEDRSbIWVKoGcUfdNQebglxp3jRB+tfx/XU0oZl2m8oewxipiNmdiREUZ -Lazh9DJoNtXkzTqzdQNfwRM+BjjVjx8IpNJV2L2IeTBxWtczFS7ggEHHQLWvYZKj -eCQgGdRwQt0V1pQ5Jt0KKkmFueTCLESvaHs9fHBtrtIhmBm1FpBZqTVUT1vvXqp7 -eIy4yFoR+j9SgWZ5kI+7myl/Bo5mycKzFE+TYiNvOWwdMnT2Uz3CZsQUcExUBd6M -tOT75Kte3yMBJmE16f/YbPItA0Cq4af3yUIxDpKwT28tAgMBAAGjdjB0MA4GA1Ud -DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T -AQH/BAIwADAfBgNVHSMEGDAWgBTWfAmQ/BUIQm/9/llJJs2jUMWzGzAUBgNVHREE -DTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAG/POnBob0S7iYwsbtI2 -3LTTbRnmseIErtJuJmI9yYzgVIm6sUSKhlIUfAIm4rfRuzE94KFeWR2w9RabxOJD -wjYLLKvQ6rFY5g2AV/J0TwDjYuq0absdaDPZ8MKJ+/lpGYK3Te+CTOfq5FJRFt1q -GOkXAxnNpGg0obeRWRKFiAMHbcw6a8LIMfRjCooo3+uSQGsbVzGxSB4CYo720KcC -9vB1K9XALwzoqCewP4aiQsMY1GWpAmzXJftY3w+lka0e9dBYcdEdOqxSoZb5OBBZ -p5e60QweRuJsb60aUaCG8HoICevXYK2fFqCQdlb5sIqQqXyN2K6HuKAFywsjsGyJ -abY= ------END CERTIFICATE-----"; - - // Configure the client - let ca = "-----BEGIN CERTIFICATE----- -MIIDSzCCAjOgAwIBAgIIB42n1ZIkOakwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgMDc4ZGE3MCAXDTIzMDMwNjE2MDMwN1oYDzIxMjMw -MzA2MTYwMzA3WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAwNzhkYTcwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIuCq24O4P4Aep5vAVlrIQ7P8+ -uWWgcHIFYa02TmhBUB/hjo0JANCQvAtpVNuQ8NyKPlqnnq1cttePbSYVeA0rrnOs -DcfySAiyGBEY9zMjFfHJtH1wtrPcJEU8XIEY3xUlrAJE2CEuV9dVYgfEEydnvgLc -8Ug0WXSiARjqbnMW3l8jh6bYCp/UpL/gSM4mxdKrgpfyPoweGhlOWXc3RTS7cqM9 -T25acURGOSI6/g8GF0sNE4VZmUvHggSTmsbLeXMJzxDWO+xVehRmbQx3IkG7u++b -QdRwGIJcDNn7zHlDMHtQ0Z1DBV94fZNBwCULhCBB5g20XTGw//S7Fj2FPwyhAgMB -AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTWfAmQ/BUIQm/9 -/llJJs2jUMWzGzAfBgNVHSMEGDAWgBTWfAmQ/BUIQm/9/llJJs2jUMWzGzANBgkq -hkiG9w0BAQsFAAOCAQEAvtcZFAELKiTuOiAeYts6zeKxc+nnHCzayDeD/BDCbxGJ -e1n+xdHjLtWGd+/Anc+fvftSYBPTFQqCi84lPiUIln5z/rUxE+ke81hNPIfw2obc -yIg87xCabQpVyEh8s+MV+7YPQ1+fH4FuSi2Fck1FejxkVqN2uOZPvOYUmSTsaVr1 -8SfRnwJNZ9UMRPM2bD4Jkvj0VcL42JM3QkOClOzYW4j/vll2cSs4kx7er27cIoo1 -Ck0v2xSPAiVjg6w65rUQeW6uB5m0T2wyj+wm0At8vzhZPlgS1fKhcmT2dzOq3+oN -R+IdLiXcyIkg0m9N8I17p0ljCSkbrgGMD3bbePRTfg== ------END CERTIFICATE-----"; + let (ca, cert, key) = get_tls_certs(); // Define the locator let mut endpoint: EndPoint = format!("quic/localhost:{}", 13040).parse().unwrap(); @@ -834,3 +689,214 @@ async fn openclose_vsock() { let endpoint: EndPoint = "vsock/VMADDR_CID_LOCAL:17000".parse().unwrap(); openclose_lowlatency_transport(&endpoint).await; } + +#[cfg(feature = "transport_quic")] +#[cfg(target_os = "linux")] +#[should_panic(expected = "Elapsed")] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn openclose_quic_only_connect_with_interface_restriction() { + use zenoh_link::quic::config::*; + + zenoh_util::init_log_from_env_or("error"); + let addrs = get_ipv4_ipaddrs(None); + let (ca, cert, key) = get_tls_certs(); + + let mut listen_endpoint: EndPoint = format!("quic/{}:{}", addrs[0], 13005).parse().unwrap(); + listen_endpoint + .config_mut() + .extend_from_iter( + [ + (TLS_ROOT_CA_CERTIFICATE_RAW, ca), + (TLS_LISTEN_PRIVATE_KEY_RAW, key), + (TLS_LISTEN_CERTIFICATE_RAW, cert), + ] + .iter() + .copied(), + ) + .unwrap(); + + let connect_endpoint: EndPoint = format!("quic/{}:{}#iface=lo", addrs[0], 13005) + .parse() + .unwrap(); + + // should not connect to local interface and external address + openclose_transport(&listen_endpoint, &connect_endpoint, false).await; +} + +#[cfg(feature = "transport_quic")] +#[cfg(target_os = "linux")] +#[should_panic(expected = "Elapsed")] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn openclose_quic_only_listen_with_interface_restriction() { + use zenoh_link::quic::config::*; + + zenoh_util::init_log_from_env_or("error"); + let addrs = get_ipv4_ipaddrs(None); + let (ca, cert, key) = get_tls_certs(); + + let mut listen_endpoint: EndPoint = format!("quic/{}:{}#iface=lo", addrs[0], 13006) + .parse() + .unwrap(); + listen_endpoint + .config_mut() + .extend_from_iter( + [ + (TLS_ROOT_CA_CERTIFICATE_RAW, ca), + (TLS_LISTEN_PRIVATE_KEY_RAW, key), + (TLS_LISTEN_CERTIFICATE_RAW, cert), + ] + .iter() + .copied(), + ) + .unwrap(); + + let connect_endpoint: EndPoint = format!("quic/{}:{}", addrs[0], 13006).parse().unwrap(); + + // should not connect to local interface and external address + openclose_transport(&listen_endpoint, &connect_endpoint, false).await; +} + +#[cfg(feature = "transport_tls")] +#[cfg(target_os = "linux")] +#[should_panic(expected = "Elapsed")] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn openclose_tls_only_connect_with_interface_restriction() { + use zenoh_link::tls::config::*; + + zenoh_util::init_log_from_env_or("error"); + let addrs = get_ipv4_ipaddrs(None); + let (ca, cert, key) = get_tls_certs(); + + let mut listen_endpoint: EndPoint = format!("tls/{}:{}", addrs[0], 13007).parse().unwrap(); + listen_endpoint + .config_mut() + .extend_from_iter( + [ + (TLS_ROOT_CA_CERTIFICATE_RAW, ca), + (TLS_LISTEN_PRIVATE_KEY_RAW, key), + (TLS_LISTEN_CERTIFICATE_RAW, cert), + ] + .iter() + .copied(), + ) + .unwrap(); + + let connect_endpoint: EndPoint = format!("tls/{}:{}#iface=lo", addrs[0], 13007) + .parse() + .unwrap(); + + // should not connect to local interface and external address + openclose_transport(&listen_endpoint, &connect_endpoint, false).await; +} + +#[cfg(feature = "transport_tls")] +#[cfg(target_os = "linux")] +#[should_panic(expected = "assertion failed: open_res.is_ok()")] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn openclose_tls_only_listen_with_interface_restriction() { + use zenoh_link::tls::config::*; + + zenoh_util::init_log_from_env_or("error"); + let addrs = get_ipv4_ipaddrs(None); + let (ca, cert, key) = get_tls_certs(); + + let mut listen_endpoint: EndPoint = format!("tls/{}:{}#iface=lo", addrs[0], 13008) + .parse() + .unwrap(); + listen_endpoint + .config_mut() + .extend_from_iter( + [ + (TLS_ROOT_CA_CERTIFICATE_RAW, ca), + (TLS_LISTEN_PRIVATE_KEY_RAW, key), + (TLS_LISTEN_CERTIFICATE_RAW, cert), + ] + .iter() + .copied(), + ) + .unwrap(); + + let connect_endpoint: EndPoint = format!("tls/{}:{}", addrs[0], 13008).parse().unwrap(); + + // should not connect to local interface and external address + openclose_transport(&listen_endpoint, &connect_endpoint, false).await; +} + +fn get_tls_certs() -> (&'static str, &'static str, &'static str) { + // NOTE: this an auto-generated pair of certificate and key. + // The target domain is localhost, so it has no real + // mapping to any existing domain. The certificate and key + // have been generated using: https://github.com/jsha/minica + let key = "-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAsfqAuhElN4HnyeqLovSd4Qe+nNv5AwCjSO+HFiF30x3vQ1Hi +qRA0UmyFlSqBnFH3TUHm4Jcad40QfrX8f11NKGZdpvKHsMYqYjZnYkRFGS2s4fQy +aDbV5M06s3UDX8ETPgY41Y8fCKTSVdi9iHkwcVrXMxUu4IBBx0C1r2GSo3gkIBnU +cELdFdaUOSbdCipJhbnkwixEr2h7PXxwba7SIZgZtRaQWak1VE9b716qe3iMuMha +Efo/UoFmeZCPu5spfwaOZsnCsxRPk2IjbzlsHTJ09lM9wmbEFHBMVAXejLTk++Sr +Xt8jASZhNen/2GzyLQNAquGn98lCMQ6SsE9vLQIDAQABAoIBAGQkKggHm6Q20L+4 +2+bNsoOqguLplpvM4RMpyx11qWE9h6GeUmWD+5yg+SysJQ9aw0ZSHWEjRD4ePji9 +lxvm2IIxzuIftp+NcM2gBN2ywhpfq9XbO/2NVR6PJ0dQQJzBG12bzKDFDdYkP0EU +WdiPL+WoEkvo0F57bAd77n6G7SZSgxYekBF+5S6rjbu5I1cEKW+r2vLehD4uFCVX +Q0Tu7TyIOE1KJ2anRb7ZXVUaguNj0/Er7EDT1+wN8KJKvQ1tYGIq/UUBtkP9nkOI +9XJd25k6m5AQPDddzd4W6/5+M7kjyVPi3CsQcpBPss6ueyecZOMaKqdWAHeEyaak +r67TofUCgYEA6GBa+YkRvp0Ept8cd5mh4gCRM8wUuhtzTQnhubCPivy/QqMWScdn +qD0OiARLAsqeoIfkAVgyqebVnxwTrKTvWe0JwpGylEVWQtpGz3oHgjST47yZxIiY +CSAaimi2CYnJZ+QB2oBkFVwNCuXdPEGX6LgnOGva19UKrm6ONsy6V9MCgYEAxBJu +fu4dGXZreARKEHa/7SQjI9ayAFuACFlON/EgSlICzQyG/pumv1FsMEiFrv6w7PRj +4AGqzyzGKXWVDRMrUNVeGPSKJSmlPGNqXfPaXRpVEeB7UQhAs5wyMrWDl8jEW7Ih +XcWhMLn1f/NOAKyrSDSEaEM+Nuu+xTifoAghvP8CgYEAlta9Fw+nihDIjT10cBo0 +38w4dOP7bFcXQCGy+WMnujOYPzw34opiue1wOlB3FIfL8i5jjY/fyzPA5PhHuSCT +Ec9xL3B9+AsOFHU108XFi/pvKTwqoE1+SyYgtEmGKKjdKOfzYA9JaCgJe1J8inmV +jwXCx7gTJVjwBwxSmjXIm+sCgYBQF8NhQD1M0G3YCdCDZy7BXRippCL0OGxVfL2R +5oKtOVEBl9NxH/3+evE5y/Yn5Mw7Dx3ZPHUcygpslyZ6v9Da5T3Z7dKcmaVwxJ+H +n3wcugv0EIHvOPLNK8npovINR6rGVj6BAqD0uZHKYYYEioQxK5rGyGkaoDQ+dgHm +qku12wKBgQDem5FvNp5iW7mufkPZMqf3sEGtu612QeqejIPFM1z7VkUgetsgPBXD +tYsqC2FtWzY51VOEKNpnfH7zH5n+bjoI9nAEAW63TK9ZKkr2hRGsDhJdGzmLfQ7v +F6/CuIw9EsAq6qIB8O88FXQqald+BZOx6AzB8Oedsz/WtMmIEmr/+Q== +-----END RSA PRIVATE KEY-----"; + + let cert = "-----BEGIN CERTIFICATE----- +MIIDLjCCAhagAwIBAgIIeUtmIdFQznMwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgMDc4ZGE3MCAXDTIzMDMwNjE2MDMxOFoYDzIxMjMw +MzA2MTYwMzE4WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCx+oC6ESU3gefJ6oui9J3hB76c2/kDAKNI74cWIXfT +He9DUeKpEDRSbIWVKoGcUfdNQebglxp3jRB+tfx/XU0oZl2m8oewxipiNmdiREUZ +Lazh9DJoNtXkzTqzdQNfwRM+BjjVjx8IpNJV2L2IeTBxWtczFS7ggEHHQLWvYZKj +eCQgGdRwQt0V1pQ5Jt0KKkmFueTCLESvaHs9fHBtrtIhmBm1FpBZqTVUT1vvXqp7 +eIy4yFoR+j9SgWZ5kI+7myl/Bo5mycKzFE+TYiNvOWwdMnT2Uz3CZsQUcExUBd6M +tOT75Kte3yMBJmE16f/YbPItA0Cq4af3yUIxDpKwT28tAgMBAAGjdjB0MA4GA1Ud +DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T +AQH/BAIwADAfBgNVHSMEGDAWgBTWfAmQ/BUIQm/9/llJJs2jUMWzGzAUBgNVHREE +DTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAG/POnBob0S7iYwsbtI2 +3LTTbRnmseIErtJuJmI9yYzgVIm6sUSKhlIUfAIm4rfRuzE94KFeWR2w9RabxOJD +wjYLLKvQ6rFY5g2AV/J0TwDjYuq0absdaDPZ8MKJ+/lpGYK3Te+CTOfq5FJRFt1q +GOkXAxnNpGg0obeRWRKFiAMHbcw6a8LIMfRjCooo3+uSQGsbVzGxSB4CYo720KcC +9vB1K9XALwzoqCewP4aiQsMY1GWpAmzXJftY3w+lka0e9dBYcdEdOqxSoZb5OBBZ +p5e60QweRuJsb60aUaCG8HoICevXYK2fFqCQdlb5sIqQqXyN2K6HuKAFywsjsGyJ +abY= +-----END CERTIFICATE-----"; + + // Configure the client + let ca = "-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIIB42n1ZIkOakwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgMDc4ZGE3MCAXDTIzMDMwNjE2MDMwN1oYDzIxMjMw +MzA2MTYwMzA3WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAwNzhkYTcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIuCq24O4P4Aep5vAVlrIQ7P8+ +uWWgcHIFYa02TmhBUB/hjo0JANCQvAtpVNuQ8NyKPlqnnq1cttePbSYVeA0rrnOs +DcfySAiyGBEY9zMjFfHJtH1wtrPcJEU8XIEY3xUlrAJE2CEuV9dVYgfEEydnvgLc +8Ug0WXSiARjqbnMW3l8jh6bYCp/UpL/gSM4mxdKrgpfyPoweGhlOWXc3RTS7cqM9 +T25acURGOSI6/g8GF0sNE4VZmUvHggSTmsbLeXMJzxDWO+xVehRmbQx3IkG7u++b +QdRwGIJcDNn7zHlDMHtQ0Z1DBV94fZNBwCULhCBB5g20XTGw//S7Fj2FPwyhAgMB +AAGjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTWfAmQ/BUIQm/9 +/llJJs2jUMWzGzAfBgNVHSMEGDAWgBTWfAmQ/BUIQm/9/llJJs2jUMWzGzANBgkq +hkiG9w0BAQsFAAOCAQEAvtcZFAELKiTuOiAeYts6zeKxc+nnHCzayDeD/BDCbxGJ +e1n+xdHjLtWGd+/Anc+fvftSYBPTFQqCi84lPiUIln5z/rUxE+ke81hNPIfw2obc +yIg87xCabQpVyEh8s+MV+7YPQ1+fH4FuSi2Fck1FejxkVqN2uOZPvOYUmSTsaVr1 +8SfRnwJNZ9UMRPM2bD4Jkvj0VcL42JM3QkOClOzYW4j/vll2cSs4kx7er27cIoo1 +Ck0v2xSPAiVjg6w65rUQeW6uB5m0T2wyj+wm0At8vzhZPlgS1fKhcmT2dzOq3+oN +R+IdLiXcyIkg0m9N8I17p0ljCSkbrgGMD3bbePRTfg== +-----END CERTIFICATE-----"; + + (ca, cert, key) +} From df86f75c321249d2e8b669e26e2c0afb1b56a744 Mon Sep 17 00:00:00 2001 From: Joseph Perez Date: Wed, 11 Dec 2024 15:36:44 +0100 Subject: [PATCH 15/23] fix: don't emit an error log if pipeline is closed (#1658) * fix: don't emit an error log if pipeline is closed Before, there was no distinction between the pipeline dropping a message because its deadline was reached or because the transport was closed. As a consequence, blocking message which was dropped because of closed transport would still emit an error log. This PR introduce the distinction between the two dropping causes. * fix: fix dumb merge resolution * fix: removed half-backed test * test: add test * fix: lint * fix: lint * fix: fix test * fix: fix test * fix: lint --- io/zenoh-transport/src/common/pipeline.rs | 93 +++++++++++++++---- io/zenoh-transport/src/multicast/mod.rs | 2 +- io/zenoh-transport/src/multicast/tx.rs | 15 +-- .../src/unicast/universal/transport.rs | 5 +- .../src/unicast/universal/tx.rs | 17 ++-- 5 files changed, 92 insertions(+), 40 deletions(-) diff --git a/io/zenoh-transport/src/common/pipeline.rs b/io/zenoh-transport/src/common/pipeline.rs index 256b4e760a..bc9319a7d1 100644 --- a/io/zenoh-transport/src/common/pipeline.rs +++ b/io/zenoh-transport/src/common/pipeline.rs @@ -12,6 +12,7 @@ // ZettaScale Zenoh Team, // use std::{ + fmt, ops::Add, sync::{ atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering}, @@ -40,7 +41,7 @@ use zenoh_protocol::{ AtomicBatchSize, BatchSize, TransportMessage, }, }; -use zenoh_sync::{event, Notifier, Waiter}; +use zenoh_sync::{event, Notifier, WaitDeadlineError, Waiter}; use super::{ batch::{Encode, WBatch}, @@ -56,6 +57,15 @@ struct StageInRefill { s_ref_r: RingBufferReader, } +#[derive(Debug)] +pub(crate) struct TransportClosed; +impl fmt::Display for TransportClosed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "transport closed") + } +} +impl std::error::Error for TransportClosed {} + impl StageInRefill { fn pull(&mut self) -> Option { self.s_ref_r.pull() @@ -65,8 +75,12 @@ impl StageInRefill { self.n_ref_r.wait().is_ok() } - fn wait_deadline(&self, instant: Instant) -> bool { - self.n_ref_r.wait_deadline(instant).is_ok() + fn wait_deadline(&self, instant: Instant) -> Result { + match self.n_ref_r.wait_deadline(instant) { + Ok(()) => Ok(true), + Err(WaitDeadlineError::Deadline) => Ok(false), + Err(WaitDeadlineError::WaitError) => Err(TransportClosed), + } } } @@ -214,9 +228,9 @@ impl Deadline { } #[inline] - fn wait(&mut self, s_ref: &StageInRefill) -> bool { + fn wait(&mut self, s_ref: &StageInRefill) -> Result { match self.lazy_deadline.deadline() { - DeadlineSetting::Immediate => false, + DeadlineSetting::Immediate => Ok(false), DeadlineSetting::Finite(instant) => s_ref.wait_deadline(*instant), } } @@ -243,7 +257,7 @@ impl StageIn { msg: &mut NetworkMessage, priority: Priority, deadline: &mut Deadline, - ) -> bool { + ) -> Result { // Lock the current serialization batch. let mut c_guard = self.mutex.current(); @@ -264,7 +278,7 @@ impl StageIn { None => { drop(c_guard); // Wait for an available batch until deadline - if !deadline.wait(&self.s_ref) { + if !deadline.wait(&self.s_ref)? { // Still no available batch. // Restore the sequence number and drop the message $($restore_sn)? @@ -272,7 +286,7 @@ impl StageIn { "Zenoh message dropped because it's over the deadline {:?}: {:?}", deadline.lazy_deadline.wait_time, msg ); - return false; + return Ok(false); } c_guard = self.mutex.current(); } @@ -287,13 +301,13 @@ impl StageIn { if !self.batching || $msg.is_express() { // Move out existing batch self.s_out.move_batch($batch); - return true; + return Ok(true); } else { let bytes = $batch.len(); *c_guard = Some($batch); drop(c_guard); self.s_out.notify(bytes); - return true; + return Ok(true); } }}; } @@ -404,7 +418,7 @@ impl StageIn { // Clean the fragbuf self.fragbuf.clear(); - true + Ok(true) } #[inline] @@ -767,7 +781,10 @@ pub(crate) struct TransmissionPipelineProducer { impl TransmissionPipelineProducer { #[inline] - pub(crate) fn push_network_message(&self, mut msg: NetworkMessage) -> bool { + pub(crate) fn push_network_message( + &self, + mut msg: NetworkMessage, + ) -> Result { // If the queue is not QoS, it means that we only have one priority with index 0. let (idx, priority) = if self.stage_in.len() > 1 { let priority = msg.priority(); @@ -780,7 +797,7 @@ impl TransmissionPipelineProducer { let (wait_time, max_wait_time) = if msg.is_droppable() { // Checked if we are blocked on the priority queue and we drop directly the message if self.status.is_congested(priority) { - return false; + return Ok(false); } (self.wait_before_drop.0, Some(self.wait_before_drop.1)) } else { @@ -789,11 +806,11 @@ impl TransmissionPipelineProducer { let mut deadline = Deadline::new(wait_time, max_wait_time); // Lock the channel. We are the only one that will be writing on it. let mut queue = zlock!(self.stage_in[idx]); - let sent = queue.push_network_message(&mut msg, priority, &mut deadline); + let sent = queue.push_network_message(&mut msg, priority, &mut deadline)?; if !sent { self.status.set_congested(priority, true); } - sent + Ok(sent) } #[inline] @@ -1002,7 +1019,7 @@ mod tests { "Pipeline Flow [>>>]: Pushed {} msgs ({payload_size} bytes)", i + 1 ); - queue.push_network_message(message.clone()); + queue.push_network_message(message.clone()).unwrap(); } } @@ -1129,7 +1146,7 @@ mod tests { println!( "Pipeline Blocking [>>>]: ({id}) Scheduling message #{i} with payload size of {payload_size} bytes" ); - queue.push_network_message(message.clone()); + queue.push_network_message(message.clone()).unwrap(); let c = counter.fetch_add(1, Ordering::AcqRel); println!( "Pipeline Blocking [>>>]: ({}) Scheduled message #{} (tot {}) with payload size of {} bytes", @@ -1173,12 +1190,13 @@ mod tests { timeout(TIMEOUT, check).await?; - // Disable and drain the queue - timeout( + // Drain the queue (but don't drop it to avoid dropping the messages) + let _consumer = timeout( TIMEOUT, task::spawn_blocking(move || { println!("Pipeline Blocking [---]: draining the queue"); let _ = consumer.drain(); + consumer }), ) .await??; @@ -1242,7 +1260,7 @@ mod tests { let duration = Duration::from_millis(5_500); let start = Instant::now(); while start.elapsed() < duration { - producer.push_network_message(message.clone()); + producer.push_network_message(message.clone()).unwrap(); } } } @@ -1269,4 +1287,39 @@ mod tests { tokio::time::sleep(Duration::from_millis(500)).await; } } + + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn tx_pipeline_closed() -> ZResult<()> { + // Pipeline + let tct = TransportPriorityTx::make(Bits::from(TransportSn::MAX))?; + let priorities = vec![tct]; + let (producer, consumer) = + TransmissionPipeline::make(CONFIG_NOT_STREAMED, priorities.as_slice()); + // Drop consumer to close the pipeline + drop(consumer); + + let message: NetworkMessage = Push { + wire_expr: "test".into(), + ext_qos: ext::QoSType::new(Priority::Control, CongestionControl::Block, true), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::DEFAULT, + payload: PushBody::Put(Put { + timestamp: None, + encoding: Encoding::empty(), + ext_sinfo: None, + #[cfg(feature = "shared-memory")] + ext_shm: None, + ext_attachment: None, + ext_unknown: vec![], + payload: vec![42u8].into(), + }), + } + .into(); + // First message should not be rejected as the is one batch available in the queue + assert!(producer.push_network_message(message.clone()).is_ok()); + // Second message should be rejected + assert!(producer.push_network_message(message.clone()).is_err()); + + Ok(()) + } } diff --git a/io/zenoh-transport/src/multicast/mod.rs b/io/zenoh-transport/src/multicast/mod.rs index fbb656264d..cd76176b50 100644 --- a/io/zenoh-transport/src/multicast/mod.rs +++ b/io/zenoh-transport/src/multicast/mod.rs @@ -113,7 +113,7 @@ impl TransportMulticast { #[inline(always)] pub fn schedule(&self, message: NetworkMessage) -> ZResult<()> { let transport = self.get_transport()?; - transport.schedule(message); + transport.schedule(message)?; Ok(()) } diff --git a/io/zenoh-transport/src/multicast/tx.rs b/io/zenoh-transport/src/multicast/tx.rs index 775131703a..dec3f4a8a9 100644 --- a/io/zenoh-transport/src/multicast/tx.rs +++ b/io/zenoh-transport/src/multicast/tx.rs @@ -13,6 +13,7 @@ // use zenoh_core::zread; use zenoh_protocol::network::NetworkMessage; +use zenoh_result::ZResult; use super::transport::TransportMulticastInner; #[cfg(feature = "shared-memory")] @@ -20,7 +21,7 @@ use crate::shm::map_zmsg_to_partner; //noinspection ALL impl TransportMulticastInner { - fn schedule_on_link(&self, msg: NetworkMessage) -> bool { + fn schedule_on_link(&self, msg: NetworkMessage) -> ZResult { macro_rules! zpush { ($guard:expr, $pipeline:expr, $msg:expr) => { // Drop the guard before the push_zenoh_message since @@ -28,7 +29,7 @@ impl TransportMulticastInner { // block for fairly long time let pl = $pipeline.clone(); drop($guard); - return pl.push_network_message($msg); + return Ok(pl.push_network_message($msg)?); }; } @@ -47,22 +48,22 @@ impl TransportMulticastInner { } } - false + Ok(false) } #[allow(unused_mut)] // When feature "shared-memory" is not enabled #[allow(clippy::let_and_return)] // When feature "stats" is not enabled #[inline(always)] - pub(super) fn schedule(&self, mut msg: NetworkMessage) -> bool { + pub(super) fn schedule(&self, mut msg: NetworkMessage) -> ZResult { #[cfg(feature = "shared-memory")] { if let Err(e) = map_zmsg_to_partner(&mut msg, &self.shm) { tracing::trace!("Failed SHM conversion: {}", e); - return false; + return Ok(false); } } - let res = self.schedule_on_link(msg); + let res = self.schedule_on_link(msg)?; #[cfg(feature = "stats")] if res { @@ -71,6 +72,6 @@ impl TransportMulticastInner { self.stats.inc_tx_n_dropped(1); } - res + Ok(res) } } diff --git a/io/zenoh-transport/src/unicast/universal/transport.rs b/io/zenoh-transport/src/unicast/universal/transport.rs index b95b68d606..a42a902164 100644 --- a/io/zenoh-transport/src/unicast/universal/transport.rs +++ b/io/zenoh-transport/src/unicast/universal/transport.rs @@ -398,10 +398,7 @@ impl TransportUnicastTrait for TransportUnicastUniversal { /* TX */ /*************************************/ fn schedule(&self, msg: NetworkMessage) -> ZResult<()> { - match self.internal_schedule(msg) { - true => Ok(()), - false => bail!("error scheduling message!"), - } + self.internal_schedule(msg).map(|_| ()) } fn add_debug_fields<'a, 'b: 'a, 'c>( diff --git a/io/zenoh-transport/src/unicast/universal/tx.rs b/io/zenoh-transport/src/unicast/universal/tx.rs index 6f61fc070d..9442bdd1c5 100644 --- a/io/zenoh-transport/src/unicast/universal/tx.rs +++ b/io/zenoh-transport/src/unicast/universal/tx.rs @@ -16,6 +16,7 @@ use zenoh_protocol::{ network::NetworkMessage, transport::close, }; +use zenoh_result::ZResult; use super::transport::TransportUnicastUniversal; #[cfg(feature = "shared-memory")] @@ -68,7 +69,7 @@ impl TransportUnicastUniversal { match_.full.or(match_.partial).or(match_.any) } - fn schedule_on_link(&self, msg: NetworkMessage) -> bool { + fn schedule_on_link(&self, msg: NetworkMessage) -> ZResult { let transport_links = self .links .read() @@ -93,7 +94,7 @@ impl TransportUnicastUniversal { ); // No Link found - return false; + return Ok(false); }; let transport_link = transport_links @@ -112,7 +113,7 @@ impl TransportUnicastUniversal { // block for fairly long time drop(transport_links); let droppable = msg.is_droppable(); - let push = pipeline.push_network_message(msg); + let push = pipeline.push_network_message(msg)?; if !push && !droppable { tracing::error!( "Unable to push non droppable network message to {}. Closing transport!", @@ -131,22 +132,22 @@ impl TransportUnicastUniversal { } }); } - push + Ok(push) } #[allow(unused_mut)] // When feature "shared-memory" is not enabled #[allow(clippy::let_and_return)] // When feature "stats" is not enabled #[inline(always)] - pub(crate) fn internal_schedule(&self, mut msg: NetworkMessage) -> bool { + pub(crate) fn internal_schedule(&self, mut msg: NetworkMessage) -> ZResult { #[cfg(feature = "shared-memory")] { if let Err(e) = map_zmsg_to_partner(&mut msg, &self.config.shm) { tracing::trace!("Failed SHM conversion: {}", e); - return false; + return Ok(false); } } - let res = self.schedule_on_link(msg); + let res = self.schedule_on_link(msg)?; #[cfg(feature = "stats")] if res { @@ -155,7 +156,7 @@ impl TransportUnicastUniversal { self.stats.inc_tx_n_dropped(1); } - res + Ok(res) } } From cc201aa1f2c0e9ec780bb6e55fdcc750434a9b2f Mon Sep 17 00:00:00 2001 From: Michael Ilyin Date: Wed, 11 Dec 2024 16:33:20 +0100 Subject: [PATCH 16/23] restored internal_config feature as part of "unstable" (#1660) --- zenoh/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zenoh/Cargo.toml b/zenoh/Cargo.toml index acc6e26176..1359bdd1b1 100644 --- a/zenoh/Cargo.toml +++ b/zenoh/Cargo.toml @@ -64,7 +64,7 @@ transport_udp = ["zenoh-transport/transport_udp"] transport_unixsock-stream = ["zenoh-transport/transport_unixsock-stream"] transport_ws = ["zenoh-transport/transport_ws"] transport_vsock = ["zenoh-transport/transport_vsock"] -unstable = ["zenoh-keyexpr/unstable", "zenoh-config/unstable"] +unstable = ["internal_config", "zenoh-keyexpr/unstable", "zenoh-config/unstable"] internal_config = [] [dependencies] From b7381b5dbcbda219212d9e38157a1f4bbce808df Mon Sep 17 00:00:00 2001 From: Luca Cominardi Date: Thu, 12 Dec 2024 11:18:01 +0100 Subject: [PATCH 17/23] Add default open timeout for new link establishment (#1663) * Add default open timeout for new link establishment * Update config docs --- DEFAULT_CONFIG.json5 | 2 + commons/zenoh-config/src/defaults.rs | 1 + commons/zenoh-config/src/lib.rs | 2 + io/zenoh-transport/src/unicast/manager.rs | 16 ++++++- zenoh/src/net/runtime/orchestrator.rs | 55 +++++------------------ 5 files changed, 32 insertions(+), 44 deletions(-) diff --git a/DEFAULT_CONFIG.json5 b/DEFAULT_CONFIG.json5 index 552107328f..a2731b9525 100644 --- a/DEFAULT_CONFIG.json5 +++ b/DEFAULT_CONFIG.json5 @@ -337,6 +337,8 @@ transport: { unicast: { /// Timeout in milliseconds when opening a link + open_timeout: 10000, + /// Timeout in milliseconds when accepting a link accept_timeout: 10000, /// Maximum number of zenoh session in pending state while accepting accept_pending: 100, diff --git a/commons/zenoh-config/src/defaults.rs b/commons/zenoh-config/src/defaults.rs index da10d7b790..195a08a073 100644 --- a/commons/zenoh-config/src/defaults.rs +++ b/commons/zenoh-config/src/defaults.rs @@ -171,6 +171,7 @@ impl Default for ConnectConfig { impl Default for TransportUnicastConf { fn default() -> Self { Self { + open_timeout: 10_000, accept_timeout: 10_000, accept_pending: 100, max_sessions: 1_000, diff --git a/commons/zenoh-config/src/lib.rs b/commons/zenoh-config/src/lib.rs index 8ab4d5d3a6..30bd9a7c81 100644 --- a/commons/zenoh-config/src/lib.rs +++ b/commons/zenoh-config/src/lib.rs @@ -374,6 +374,8 @@ validated_struct::validator! { TransportConf { pub unicast: TransportUnicastConf { /// Timeout in milliseconds when opening a link (default: 10000). + open_timeout: u64, + /// Timeout in milliseconds when accepting a link (default: 10000). accept_timeout: u64, /// Number of links that may stay pending during accept phase (default: 100). accept_pending: usize, diff --git a/io/zenoh-transport/src/unicast/manager.rs b/io/zenoh-transport/src/unicast/manager.rs index 7497604757..c2cf858327 100644 --- a/io/zenoh-transport/src/unicast/manager.rs +++ b/io/zenoh-transport/src/unicast/manager.rs @@ -60,6 +60,7 @@ use crate::{ pub struct TransportManagerConfigUnicast { pub lease: Duration, pub keep_alive: usize, + pub open_timeout: Duration, pub accept_timeout: Duration, pub accept_pending: usize, pub max_sessions: usize, @@ -105,6 +106,7 @@ pub struct TransportManagerBuilderUnicast { // target interval. pub(super) lease: Duration, pub(super) keep_alive: usize, + pub(super) open_timeout: Duration, pub(super) accept_timeout: Duration, pub(super) accept_pending: usize, pub(super) max_sessions: usize, @@ -131,6 +133,11 @@ impl TransportManagerBuilderUnicast { self } + pub fn open_timeout(mut self, open_timeout: Duration) -> Self { + self.open_timeout = open_timeout; + self + } + pub fn accept_timeout(mut self, accept_timeout: Duration) -> Self { self.accept_timeout = accept_timeout; self @@ -225,6 +232,7 @@ impl TransportManagerBuilderUnicast { let config = TransportManagerConfigUnicast { lease: self.lease, keep_alive: self.keep_alive, + open_timeout: self.open_timeout, accept_timeout: self.accept_timeout, accept_pending: self.accept_pending, max_sessions: self.max_sessions, @@ -274,6 +282,7 @@ impl Default for TransportManagerBuilderUnicast { Self { lease: Duration::from_millis(*link_tx.lease()), keep_alive: *link_tx.keep_alive(), + open_timeout: Duration::from_millis(*transport.open_timeout()), accept_timeout: Duration::from_millis(*transport.accept_timeout()), accept_pending: *transport.accept_pending(), max_sessions: *transport.max_sessions(), @@ -725,7 +734,12 @@ impl TransportManager { // Create a new link associated by calling the Link Manager let link = manager.new_link(endpoint.clone()).await?; // Open the link - super::establishment::open::open_link(endpoint, link, self).await + tokio::time::timeout( + self.config.unicast.open_timeout, + super::establishment::open::open_link(endpoint, link, self), + ) + .await + .map_err(|e| zerror!("{e}"))? } pub async fn get_transport_unicast(&self, peer: &ZenohIdProto) -> Option { diff --git a/zenoh/src/net/runtime/orchestrator.rs b/zenoh/src/net/runtime/orchestrator.rs index bd3cf0a155..15f7ca52a4 100644 --- a/zenoh/src/net/runtime/orchestrator.rs +++ b/zenoh/src/net/runtime/orchestrator.rs @@ -349,11 +349,7 @@ impl Runtime { ); if retry_config.timeout().is_zero() || self.get_global_connect_timeout().is_zero() { // try to connect and exit immediately without retry - if self - .peer_connector(endpoint, retry_config.timeout()) - .await - .is_ok() - { + if self.peer_connector(endpoint).await.is_ok() { return Ok(()); } } else { @@ -379,7 +375,7 @@ impl Runtime { ); if retry_config.timeout().is_zero() || self.get_global_connect_timeout().is_zero() { // try to connect and exit immediately without retry - if let Err(e) = self.peer_connector(endpoint, retry_config.timeout()).await { + if let Err(e) = self.peer_connector(endpoint).await { if retry_config.exit_on_failure { return Err(e); } @@ -398,18 +394,12 @@ impl Runtime { Ok(()) } - async fn peer_connector(&self, peer: EndPoint, timeout: std::time::Duration) -> ZResult<()> { - match tokio::time::timeout(timeout, self.manager().open_transport_unicast(peer.clone())) - .await - { - Ok(Ok(_)) => Ok(()), - Ok(Err(e)) => { - tracing::warn!("Unable to connect to {}! {}", peer, e); - Err(e) - } + async fn peer_connector(&self, peer: EndPoint) -> ZResult<()> { + match self.manager().open_transport_unicast(peer.clone()).await { + Ok(_) => Ok(()), Err(e) => { tracing::warn!("Unable to connect to {}! {}", peer, e); - Err(e.into()) + Err(e) } } } @@ -795,9 +785,9 @@ impl Runtime { tracing::trace!("Trying to connect to configured peer {}", peer); let endpoint = peer.clone(); tokio::select! { - res = tokio::time::timeout(retry_config.timeout(), self.manager().open_transport_unicast(endpoint)) => { + res = self.manager().open_transport_unicast(endpoint) => { match res { - Ok(Ok(transport)) => { + Ok(transport) => { tracing::debug!("Successfully connected to configured peer {}", peer); if let Ok(Some(orch_transport)) = transport.get_callback() { if let Some(orch_transport) = orch_transport @@ -809,14 +799,6 @@ impl Runtime { } return transport.get_zid(); } - Ok(Err(e)) => { - tracing::debug!( - "Unable to connect to configured peer {}! {}. Retry in {:?}.", - peer, - e, - period.duration() - ); - } Err(e) => { tracing::debug!( "Unable to connect to configured peer {}! {}. Retry in {:?}.", @@ -977,7 +959,6 @@ impl Runtime { }; let endpoint = locator.to_owned().into(); - let retry_config = self.get_connect_retry_config(&endpoint); let priorities = locator .metadata() .get(Metadata::PRIORITIES) @@ -997,35 +978,23 @@ impl Runtime { }) { if is_multicast { - match tokio::time::timeout( - retry_config.timeout(), - manager.open_transport_multicast(endpoint), - ) - .await - { - Ok(Ok(transport)) => { + match manager.open_transport_multicast(endpoint).await { + Ok(transport) => { tracing::debug!( "Successfully connected to newly scouted peer: {:?}", transport ); } - Ok(Err(e)) => tracing::trace!("{} {} on {}: {}", ERR, zid, locator, e), Err(e) => tracing::trace!("{} {} on {}: {}", ERR, zid, locator, e), } } else { - match tokio::time::timeout( - retry_config.timeout(), - manager.open_transport_unicast(endpoint), - ) - .await - { - Ok(Ok(transport)) => { + match manager.open_transport_unicast(endpoint).await { + Ok(transport) => { tracing::debug!( "Successfully connected to newly scouted peer: {:?}", transport ); } - Ok(Err(e)) => tracing::trace!("{} {} on {}: {}", ERR, zid, locator, e), Err(e) => tracing::trace!("{} {} on {}: {}", ERR, zid, locator, e), } } From 8716c870ef3eb574239e08f4cfd96aaacef3e726 Mon Sep 17 00:00:00 2001 From: "ChenYing Kuo (CY)" Date: Thu, 12 Dec 2024 18:25:53 +0800 Subject: [PATCH 18/23] Add CI for checking markdown format. (#1662) Signed-off-by: ChenYing Kuo --- .github/workflows/ci.yml | 10 ++++++++++ .markdownlint.yaml | 6 ++++++ commons/zenoh-buffers/README.md | 2 -- commons/zenoh-codec/README.md | 2 -- commons/zenoh-collections/README.md | 2 -- commons/zenoh-config/README.md | 2 -- commons/zenoh-core/README.md | 2 -- commons/zenoh-crypto/README.md | 2 -- commons/zenoh-keyexpr/README.md | 2 -- commons/zenoh-macros/README.md | 2 -- commons/zenoh-protocol/README.md | 2 -- commons/zenoh-result/README.md | 2 -- commons/zenoh-runtime/README.md | 2 -- commons/zenoh-shm/README.md | 2 -- commons/zenoh-sync/README.md | 2 -- commons/zenoh-task/README.md | 2 -- commons/zenoh-util/README.md | 2 -- io/zenoh-link-commons/README.md | 2 -- io/zenoh-link/README.md | 2 -- io/zenoh-links/README.md | 2 -- io/zenoh-links/zenoh-link-quic/README.md | 2 -- io/zenoh-links/zenoh-link-serial/README.md | 2 -- io/zenoh-links/zenoh-link-tcp/README.md | 2 -- io/zenoh-links/zenoh-link-tls/README.md | 2 -- io/zenoh-links/zenoh-link-udp/README.md | 2 -- io/zenoh-links/zenoh-link-unixpipe/README.md | 2 -- .../zenoh-link-unixsock_stream/README.md | 2 -- io/zenoh-links/zenoh-link-vsock/README.md | 2 -- io/zenoh-links/zenoh-link-ws/README.md | 2 -- io/zenoh-transport/README.md | 2 -- plugins/zenoh-backend-traits/README.md | 2 -- plugins/zenoh-plugin-example/README.md | 2 -- plugins/zenoh-plugin-rest/README.md | 2 -- .../zenoh-plugin-storage-manager/README.md | 2 -- plugins/zenoh-plugin-trait/README.md | 2 -- zenoh-ext/README.md | 2 -- zenoh-ext/examples/examples/README.md | 20 +++++++++++++------ 37 files changed, 30 insertions(+), 74 deletions(-) create mode 100644 .markdownlint.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbad34c4a6..28ccb7b8c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -186,6 +186,16 @@ jobs: - name: Check spelling uses: crate-ci/typos@master + + markdown_lint: + runs-on: ubuntu-latest + steps: + - uses: articulate/actions-markdownlint@v1 + with: + config: .markdownlint.yaml + files: '*.md' + ignore: target + version: 0.28.1 # NOTE: In GitHub repository settings, the "Require status checks to pass # before merging" branch protection rule ensures that commits are only merged diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000000..12f7379454 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,6 @@ +{ + "MD013": false, # Line length limitation + "MD033": false, # Enable Inline HTML + "MD041": false, # Allow first line heading + "MD045": false, # Allow Images have no alternate text +} \ No newline at end of file diff --git a/commons/zenoh-buffers/README.md b/commons/zenoh-buffers/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-buffers/README.md +++ b/commons/zenoh-buffers/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-codec/README.md b/commons/zenoh-codec/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-codec/README.md +++ b/commons/zenoh-codec/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-collections/README.md b/commons/zenoh-collections/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-collections/README.md +++ b/commons/zenoh-collections/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-config/README.md b/commons/zenoh-config/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-config/README.md +++ b/commons/zenoh-config/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-core/README.md b/commons/zenoh-core/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-core/README.md +++ b/commons/zenoh-core/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-crypto/README.md b/commons/zenoh-crypto/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-crypto/README.md +++ b/commons/zenoh-crypto/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-keyexpr/README.md b/commons/zenoh-keyexpr/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-keyexpr/README.md +++ b/commons/zenoh-keyexpr/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-macros/README.md b/commons/zenoh-macros/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-macros/README.md +++ b/commons/zenoh-macros/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-protocol/README.md b/commons/zenoh-protocol/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-protocol/README.md +++ b/commons/zenoh-protocol/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-result/README.md b/commons/zenoh-result/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-result/README.md +++ b/commons/zenoh-result/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-runtime/README.md b/commons/zenoh-runtime/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-runtime/README.md +++ b/commons/zenoh-runtime/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-shm/README.md b/commons/zenoh-shm/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-shm/README.md +++ b/commons/zenoh-shm/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-sync/README.md b/commons/zenoh-sync/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-sync/README.md +++ b/commons/zenoh-sync/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-task/README.md b/commons/zenoh-task/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-task/README.md +++ b/commons/zenoh-task/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/commons/zenoh-util/README.md b/commons/zenoh-util/README.md index 65a29107b9..e97cecd513 100644 --- a/commons/zenoh-util/README.md +++ b/commons/zenoh-util/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-link-commons/README.md b/io/zenoh-link-commons/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-link-commons/README.md +++ b/io/zenoh-link-commons/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-link/README.md b/io/zenoh-link/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-link/README.md +++ b/io/zenoh-link/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/README.md b/io/zenoh-links/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/README.md +++ b/io/zenoh-links/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-quic/README.md b/io/zenoh-links/zenoh-link-quic/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-quic/README.md +++ b/io/zenoh-links/zenoh-link-quic/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-serial/README.md b/io/zenoh-links/zenoh-link-serial/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-serial/README.md +++ b/io/zenoh-links/zenoh-link-serial/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-tcp/README.md b/io/zenoh-links/zenoh-link-tcp/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-tcp/README.md +++ b/io/zenoh-links/zenoh-link-tcp/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-tls/README.md b/io/zenoh-links/zenoh-link-tls/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-tls/README.md +++ b/io/zenoh-links/zenoh-link-tls/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-udp/README.md b/io/zenoh-links/zenoh-link-udp/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-udp/README.md +++ b/io/zenoh-links/zenoh-link-udp/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-unixpipe/README.md b/io/zenoh-links/zenoh-link-unixpipe/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-unixpipe/README.md +++ b/io/zenoh-links/zenoh-link-unixpipe/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-unixsock_stream/README.md b/io/zenoh-links/zenoh-link-unixsock_stream/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-unixsock_stream/README.md +++ b/io/zenoh-links/zenoh-link-unixsock_stream/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-vsock/README.md b/io/zenoh-links/zenoh-link-vsock/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-vsock/README.md +++ b/io/zenoh-links/zenoh-link-vsock/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-links/zenoh-link-ws/README.md b/io/zenoh-links/zenoh-link-ws/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-links/zenoh-link-ws/README.md +++ b/io/zenoh-links/zenoh-link-ws/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/io/zenoh-transport/README.md b/io/zenoh-transport/README.md index 65a29107b9..e97cecd513 100644 --- a/io/zenoh-transport/README.md +++ b/io/zenoh-transport/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/plugins/zenoh-backend-traits/README.md b/plugins/zenoh-backend-traits/README.md index 65a29107b9..e97cecd513 100644 --- a/plugins/zenoh-backend-traits/README.md +++ b/plugins/zenoh-backend-traits/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/plugins/zenoh-plugin-example/README.md b/plugins/zenoh-plugin-example/README.md index 65a29107b9..e97cecd513 100644 --- a/plugins/zenoh-plugin-example/README.md +++ b/plugins/zenoh-plugin-example/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/plugins/zenoh-plugin-rest/README.md b/plugins/zenoh-plugin-rest/README.md index 65a29107b9..e97cecd513 100644 --- a/plugins/zenoh-plugin-rest/README.md +++ b/plugins/zenoh-plugin-rest/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/plugins/zenoh-plugin-storage-manager/README.md b/plugins/zenoh-plugin-storage-manager/README.md index 65a29107b9..e97cecd513 100644 --- a/plugins/zenoh-plugin-storage-manager/README.md +++ b/plugins/zenoh-plugin-storage-manager/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/plugins/zenoh-plugin-trait/README.md b/plugins/zenoh-plugin-trait/README.md index 65a29107b9..e97cecd513 100644 --- a/plugins/zenoh-plugin-trait/README.md +++ b/plugins/zenoh-plugin-trait/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/zenoh-ext/README.md b/zenoh-ext/README.md index 65a29107b9..e97cecd513 100644 --- a/zenoh-ext/README.md +++ b/zenoh-ext/README.md @@ -4,5 +4,3 @@ This crate is intended for Zenoh's internal use. - [Click here for Zenoh's main repository](https://github.com/eclipse-zenoh/zenoh) - [Click here for Zenoh's documentation](https://zenoh.io) - - diff --git a/zenoh-ext/examples/examples/README.md b/zenoh-ext/examples/examples/README.md index b72e50f816..1387496602 100644 --- a/zenoh-ext/examples/examples/README.md +++ b/zenoh-ext/examples/examples/README.md @@ -3,6 +3,7 @@ ## Start instructions When zenoh is built in release mode: + ```bash ./target/release/example/ ``` @@ -22,12 +23,15 @@ or retransmission. Typical usage: + ```bash - z_advanced_pub + z_advanced_pub ``` + or + ```bash - z_advanced_pub --history 10 + z_advanced_pub --history 10 ``` ### z_advanced_sub @@ -38,8 +42,9 @@ sample loss and ask for retransmission. Typical usage: + ```bash - z_advanced_sub + z_advanced_sub ``` ### z_member @@ -47,9 +52,11 @@ Group Management example: join a group and display the received group events (Join, Leave, LeaseExpired), as well as an updated group view. Typical usage: + ```bash - z_member + z_member ``` + (start/stop several in parallel) ### z_view_size @@ -57,8 +64,9 @@ Group Management example: join a group and wait for the group view to reach a configurable size (default: 3 members). Typical usage: + ```bash - z_view_size + z_view_size ``` - (start/stop several in parallel) + (start/stop several in parallel) From 77923a2e084b0e7f4fa316978a9a6226cc4d4031 Mon Sep 17 00:00:00 2001 From: OlivierHecart Date: Thu, 12 Dec 2024 16:27:54 +0100 Subject: [PATCH 19/23] Fix stats bugs (#1664) * Fix stats * Fix stats --- io/zenoh-transport/src/common/batch.rs | 27 +++++++++++++++++++--- zenoh/src/net/routing/dispatcher/pubsub.rs | 8 +++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/io/zenoh-transport/src/common/batch.rs b/io/zenoh-transport/src/common/batch.rs index 65150f728a..0a0a41cf91 100644 --- a/io/zenoh-transport/src/common/batch.rs +++ b/io/zenoh-transport/src/common/batch.rs @@ -363,7 +363,14 @@ impl Encode<&TransportMessage> for &mut WBatch { fn encode(self, x: &TransportMessage) -> Self::Output { let mut writer = self.buffer.writer(); - self.codec.write(&mut writer, x) + let res = self.codec.write(&mut writer, x); + #[cfg(feature = "stats")] + { + if res.is_ok() { + self.stats.t_msgs += 1; + } + } + res } } @@ -381,7 +388,14 @@ impl Encode<(&NetworkMessage, &FrameHeader)> for &mut WBatch { fn encode(self, x: (&NetworkMessage, &FrameHeader)) -> Self::Output { let mut writer = self.buffer.writer(); - self.codec.write(&mut writer, x) + let res = self.codec.write(&mut writer, x); + #[cfg(feature = "stats")] + { + if res.is_ok() { + self.stats.t_msgs += 1; + } + } + res } } @@ -390,7 +404,14 @@ impl Encode<(&mut ZBufReader<'_>, &mut FragmentHeader)> for &mut WBatch { fn encode(self, x: (&mut ZBufReader<'_>, &mut FragmentHeader)) -> Self::Output { let mut writer = self.buffer.writer(); - self.codec.write(&mut writer, x) + let res = self.codec.write(&mut writer, x); + #[cfg(feature = "stats")] + { + if res.is_ok() { + self.stats.t_msgs += 1; + } + } + res } } diff --git a/zenoh/src/net/routing/dispatcher/pubsub.rs b/zenoh/src/net/routing/dispatcher/pubsub.rs index c755e26a4e..4c5ff66c8f 100644 --- a/zenoh/src/net/routing/dispatcher/pubsub.rs +++ b/zenoh/src/net/routing/dispatcher/pubsub.rs @@ -434,9 +434,9 @@ pub fn route_data( drop(tables); #[cfg(feature = "stats")] if !admin { - inc_stats!(face, tx, user, msg.payload) + inc_stats!(outface, tx, user, msg.payload) } else { - inc_stats!(face, tx, admin, msg.payload) + inc_stats!(outface, tx, admin, msg.payload) } outface.primitives.send_push( @@ -465,9 +465,9 @@ pub fn route_data( for (outface, key_expr, context) in route { #[cfg(feature = "stats")] if !admin { - inc_stats!(face, tx, user, msg.payload) + inc_stats!(outface, tx, user, msg.payload) } else { - inc_stats!(face, tx, admin, msg.payload) + inc_stats!(outface, tx, admin, msg.payload) } outface.primitives.send_push( From 3744075e5afb10054165a6eac9f4c2fa0be240ac Mon Sep 17 00:00:00 2001 From: Diogo Matsubara Date: Tue, 17 Dec 2024 11:30:17 +0100 Subject: [PATCH 20/23] fix: pin home to 0.5.9 (#1673) 0.5.11 requires rustc 1.81 so we pin the dependency to the latest one working with the 1.70 series. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c0826648ba..8ad505ec67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ git-version = "0.3.9" hashbrown = "0.14" hex = { version = "0.4.3", default-features = false } # Default features are disabled due to usage in no_std crates hmac = { version = "0.12.1", features = ["std"] } -home = "0.5.9" +home = "=0.5.9" http-types = "2.12.0" humantime = "2.1.0" itertools = "0.13.0" From f6f7ea4867718c944cdfb4c57f7751858ad1facf Mon Sep 17 00:00:00 2001 From: Diogo Matsubara Date: Wed, 18 Dec 2024 09:41:30 +0100 Subject: [PATCH 21/23] fix: exclude internal from automatic changelog (#1677) Don't include PRs labeled as internal in the automatically generated changelog --- .github/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/release.yml b/.github/release.yml index 23d56cfb1a..92919932b9 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -32,4 +32,7 @@ changelog: - dependencies - title: Other changes labels: - - "*" \ No newline at end of file + - "*" + exclude: + labels: + - internal \ No newline at end of file From f3f092450d3ec12c7879e25958b73dd3ce089541 Mon Sep 17 00:00:00 2001 From: "ChenYing Kuo (CY)" Date: Wed, 18 Dec 2024 16:49:30 +0800 Subject: [PATCH 22/23] Switch to another markdown lint CI check (#1675) Signed-off-by: ChenYing Kuo --- .github/workflows/ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28ccb7b8c1..24159075a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,12 +190,11 @@ jobs: markdown_lint: runs-on: ubuntu-latest steps: - - uses: articulate/actions-markdownlint@v1 + - uses: actions/checkout@v4 + - uses: DavidAnson/markdownlint-cli2-action@v18 with: - config: .markdownlint.yaml - files: '*.md' - ignore: target - version: 0.28.1 + config: '.markdownlint.yaml' + globs: '**/README.md' # NOTE: In GitHub repository settings, the "Require status checks to pass # before merging" branch protection rule ensures that commits are only merged @@ -205,7 +204,7 @@ jobs: ci: name: CI status checks runs-on: ubuntu-latest - needs: [check, test, valgrind, typos] + needs: [check, test, valgrind, typos, markdown_lint] if: always() steps: - name: Check whether all jobs pass From a6bc509b5997c922a56253455149858af715edfa Mon Sep 17 00:00:00 2001 From: yellowhatter <104833606+yellowhatter@users.noreply.github.com> Date: Wed, 18 Dec 2024 13:28:14 +0300 Subject: [PATCH 23/23] Shm mutation example (#1672) Add SHM buffer mutation functionality to examples Add payload_mut accessor to ReplyError --- examples/examples/z_sub_shm.rs | 46 +++++++++++++--------------------- zenoh/src/api/query.rs | 6 +++++ 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/examples/examples/z_sub_shm.rs b/examples/examples/z_sub_shm.rs index f49b0628bf..98137b7dcb 100644 --- a/examples/examples/z_sub_shm.rs +++ b/examples/examples/z_sub_shm.rs @@ -14,6 +14,8 @@ use std::borrow::Cow; use clap::Parser; +#[cfg(all(feature = "shared-memory", feature = "unstable"))] +use zenoh::shm::{zshm, zshmmut}; use zenoh::{bytes::ZBytes, config::Config, key_expr::KeyExpr}; use zenoh_examples::CommonArgs; @@ -31,43 +33,25 @@ async fn main() { let subscriber = session.declare_subscriber(&key_expr).await.unwrap(); println!("Press CTRL-C to quit..."); - while let Ok(sample) = subscriber.recv_async().await { + while let Ok(mut sample) = subscriber.recv_async().await { + let kind = sample.kind(); + let key_str = sample.key_expr().as_str().to_owned(); + // Print overall payload information - let (payload_type, payload) = handle_bytes(sample.payload()); + let (payload_type, payload) = handle_bytes(sample.payload_mut()); print!( ">> [Subscriber] Received {} ('{}': '{}') [{}] ", - sample.kind(), - sample.key_expr().as_str(), - payload, - payload_type, + kind, key_str, payload, payload_type, ); // Print attachment information - if let Some(att) = sample.attachment() { + if let Some(att) = sample.attachment_mut() { let (attachment_type, attachment) = handle_bytes(att); print!(" ({}: {})", attachment_type, attachment); } println!(); } - - // // Try to get a mutable reference to the SHM buffer. If this subscriber is the only subscriber - // // holding a reference to the SHM buffer, then it will be able to get a mutable reference to it. - // // With the mutable reference at hand, it's possible to mutate in place the SHM buffer content. - - // while let Ok(mut sample) = subscriber.recv_async().await { - // let kind = sample.kind(); - // let key_expr = sample.key_expr().to_string(); - // match sample.payload_mut().as_shm_mut() { - // Ok(payload) => println!( - // ">> [Subscriber] Received {} ('{}': '{:02x?}')", - // kind, key_expr, payload - // ), - // Err(e) => { - // println!(">> [Subscriber] Not a ShmBufInner: {:?}", e); - // } - // } - // } } #[derive(clap::Parser, Clone, PartialEq, Eq, Hash, Debug)] @@ -84,7 +68,7 @@ fn parse_args() -> (Config, KeyExpr<'static>) { (args.common.into(), args.key) } -fn handle_bytes(bytes: &ZBytes) -> (&str, Cow) { +fn handle_bytes(bytes: &mut ZBytes) -> (&str, Cow) { // Determine buffer type for indication purpose let bytes_type = { // if Zenoh is built without SHM support, the only buffer type it can receive is RAW @@ -100,10 +84,14 @@ fn handle_bytes(bytes: &ZBytes) -> (&str, Cow) { "UNKNOWN" } - // if Zenoh is built with SHM support and with SHM API we can detect the exact buffer type + // if Zenoh is built with SHM support and with SHM API we can detect the exact buffer type #[cfg(all(feature = "shared-memory", feature = "unstable"))] - match bytes.as_shm() { - Some(_) => "SHM", + match bytes.as_shm_mut() { + // try to mutate SHM buffer to get it's mutability property + Some(shm) => match <&mut zshm as TryInto<&mut zshmmut>>::try_into(shm) { + Ok(_shm_mut) => "SHM (MUT)", + Err(_) => "SHM (IMMUT)", + }, None => "RAW", } }; diff --git a/zenoh/src/api/query.rs b/zenoh/src/api/query.rs index 3e1f66ed12..1d763dece8 100644 --- a/zenoh/src/api/query.rs +++ b/zenoh/src/api/query.rs @@ -86,6 +86,12 @@ impl ReplyError { &self.payload } + /// Gets the mutable payload of this ReplyError. + #[inline] + pub fn payload_mut(&mut self) -> &mut ZBytes { + &mut self.payload + } + /// Gets the encoding of this ReplyError. #[inline] pub fn encoding(&self) -> &Encoding {