diff --git a/README.md b/README.md
index 278ebc26..621aa7ac 100644
--- a/README.md
+++ b/README.md
@@ -68,11 +68,26 @@ ros2 run demo_nodes_cpp listener
The listener node should start receiving messages over the `/chatter` topic.
## Configuration
-`rmw_zenoh` relies on separate configurations files to configure the Zenoh `router` and `session` respectively.
-To understand more about `routers` and `sessions`, see [Zenoh documentation](https://zenoh.io/docs/getting-started/deployment/).
+
+By default, `Zenoh sessions` created by `rmw_zenoh` will attempt to connect to a Zenoh router to receive discovery information.
+To understand more about `Zenoh routers` and `Zenoh sessions`, see [Zenoh documentation](https://zenoh.io/docs/getting-started/deployment/).
+
+### Checking for a Zenoh router.
+The `ZENOH_ROUTER_CHECK_ATTEMPTS` environment variable can be used to configure if and how a `Zenoh session` checks for the presence of a `Zenoh router`.
+The behavior is explained in the table below.
+
+
+| ZENOH_ROUTER_CHECK_ATTEMPTS | Session behavior |
+|:---------------------------:|:----------------------------------------------------------------------------------------------------------------:|
+| unset or 0 | Indefinitely waits for connection to a Zenoh router. |
+| < 0 | Skips Zenoh router check. |
+| > 0 | Attempts to connect to a Zenoh router in `ZENOH_ROUTER_CHECK_ATTEMPTS` attempts with 1 second wait between checks. |
+
+### Session and Router configs
+`rmw_zenoh` relies on separate configurations files to configure the `Zenoh router` and `Zenoh session` respectively.
For more information on the topology of Zenoh adopted in `rmw_zenoh`, please see [Design](#design).
Default configuration files are used by `rmw_zenoh` however certain environment variables may be set to provide absolute paths to custom configuration files.
-The table below summarizes the default files and the environment variables for the `router` and `session`.
+The table below summarizes the default files and the environment variables for the `Zenoh router` and `Zenoh session`.
For a complete list of configurable parameters, see [zenoh/DEFAULT_CONFIG.json5](https://github.com/eclipse-zenoh/zenoh/blob/main/DEFAULT_CONFIG.json5).
| | Default config | Envar for custom config |
@@ -80,16 +95,16 @@ For a complete list of configurable parameters, see [zenoh/DEFAULT_CONFIG.json5]
| Router | [DEFAULT_RMW_ZENOH_ROUTER_CONFIG.json5](rmw_zenoh_cpp/config/DEFAULT_RMW_ZENOH_ROUTER_CONFIG.json5) | `ZENOH_ROUTER_CONFIG_URI` |
| Session | [DEFAULT_RMW_ZENOH_SESSION_CONFIG.json5](rmw_zenoh_cpp/config/DEFAULT_RMW_ZENOH_SESSION_CONFIG.json5) | `ZENOH_SESSION_CONFIG_URI` |
-For example, to set the path to a custom `router` configuration file,
+For example, to set the path to a custom `Zenoh router` configuration file,
```bash
export ZENOH_ROUTER_CONFIG_URI=$HOME/MY_ZENOH_ROUTER_CONFIG.json5
```
### Connecting multiple hosts
-By default, all discovery traffic is local per host, where the host is the PC running a Zenoh `router`.
-To bridge communications across two hosts, the `router` configuration for one the hosts must be updated to connect to the other `router` at startup.
-This is done by specifying an endpoint in host's `router` configuration file to as seen below.
-In this example, the `router` will connect to the `router` running on a second host with IP address `192.168.1.1` and port `7447`.
+By default, all discovery traffic is local per host, where the host is the PC running a `Zenoh router`.
+To bridge communications across two hosts, the `Zenoh router` configuration for one the hosts must be updated to connect to the other `Zenoh router` at startup.
+This is done by specifying an endpoint in host's `Zenoh router` configuration file to as seen below.
+In this example, the `Zenoh router` will connect to the `Zenoh router` running on a second host with IP address `192.168.1.1` and port `7447`.
```json
{
@@ -99,4 +114,4 @@ In this example, the `router` will connect to the `router` running on a second h
}
```
-> Note: To connect multiple hosts, include the endpoints of all routers in the network.
+> Note: To connect multiple hosts, include the endpoints of all `Zenoh routers` in the network.
diff --git a/rmw_zenoh_cpp/package.xml b/rmw_zenoh_cpp/package.xml
index 67b488be..6304ab32 100644
--- a/rmw_zenoh_cpp/package.xml
+++ b/rmw_zenoh_cpp/package.xml
@@ -10,6 +10,7 @@
ament_cmake
zenoh_c_vendor
+ zenoh_c_vendor
ament_index_cpp
fastcdr
@@ -23,6 +24,8 @@
ament_lint_auto
ament_lint_common
+ rmw_implementation_packages
+
ament_cmake
diff --git a/rmw_zenoh_cpp/src/detail/zenoh_config.cpp b/rmw_zenoh_cpp/src/detail/zenoh_config.cpp
index 2db5eb43..de0b5a0c 100644
--- a/rmw_zenoh_cpp/src/detail/zenoh_config.cpp
+++ b/rmw_zenoh_cpp/src/detail/zenoh_config.cpp
@@ -17,6 +17,7 @@
#include
#include
+#include
#include
#include
@@ -35,6 +36,8 @@ static const std::unordered_mapsecond.first, default_config_path.c_str(), config);
}
+
+///==============================================================================
+std::optional zenoh_router_check_attempts()
+{
+ const char * envar_value;
+ // The default value is to check indefinitely.
+ uint64_t default_value = std::numeric_limits::max();
+
+ if (NULL != rcutils_get_env(router_check_attempts_envar, &envar_value)) {
+ // NULL is returned if everything is ok.
+ RCUTILS_LOG_ERROR_NAMED(
+ "rmw_zenoh_cpp", "Envar %s cannot be read. Report this bug.",
+ router_check_attempts_envar);
+ return default_value;
+ }
+ // If the environment variable contains a value, handle it accordingly.
+ if (envar_value[0] != '\0') {
+ const auto read_value = std::strtol(envar_value, nullptr, 10);
+ if (read_value > 0) {
+ return read_value;
+ } else if (read_value < 0) {
+ // If less than 0, we skip the check.
+ return std::nullopt;
+ }
+ // If the value is 0, check indefinitely.
+ return default_value;
+ }
+
+ // If unset, check indefinitely.
+ return default_value;
+}
diff --git a/rmw_zenoh_cpp/src/detail/zenoh_config.hpp b/rmw_zenoh_cpp/src/detail/zenoh_config.hpp
index 1fff73cd..1a2c6a59 100644
--- a/rmw_zenoh_cpp/src/detail/zenoh_config.hpp
+++ b/rmw_zenoh_cpp/src/detail/zenoh_config.hpp
@@ -17,6 +17,7 @@
#include
+#include
#include
#include
@@ -43,4 +44,15 @@ enum class ConfigurableEntity : uint8_t
[[nodiscard]]
rmw_ret_t get_z_config(const ConfigurableEntity & entity, z_owned_config_t * config);
+///==============================================================================
+/// Get the number of times rmw_init should try to connect to a zenoh router
+/// based on the environment variable ZENOH_ROUTER_CHECK_ATTEMPTS.
+/// @details The behavior is as follows:
+/// - If not set or 0, the max value is returned.
+/// - If less than 0, std::nullopt is returned.
+/// - Else value of environemnt variable is returned.
+/// @return The number of times to try connecting to a zenoh router and
+/// std::nullopt if establishing a connection to a router is not required.
+std::optional zenoh_router_check_attempts();
+
#endif // DETAIL__ZENOH_CONFIG_HPP_
diff --git a/rmw_zenoh_cpp/src/detail/zenoh_router_check.cpp b/rmw_zenoh_cpp/src/detail/zenoh_router_check.cpp
index 5e674089..5f12e89d 100644
--- a/rmw_zenoh_cpp/src/detail/zenoh_router_check.cpp
+++ b/rmw_zenoh_cpp/src/detail/zenoh_router_check.cpp
@@ -21,7 +21,9 @@
#include
#include
+#include "liveliness_utils.hpp"
+///=============================================================================
rmw_ret_t zenoh_router_check(z_session_t session)
{
// Initialize context for callback
@@ -29,9 +31,12 @@ rmw_ret_t zenoh_router_check(z_session_t session)
// Define callback
auto callback = [](const struct z_id_t * id, void * ctx) {
+ const std::string id_str = liveliness::zid_to_str(*id);
+ RCUTILS_LOG_INFO_NAMED(
+ "rmw_zenoh_cpp",
+ "Successfully connected to a Zenoh router with id %s.", id_str.c_str());
// Note: Callback is guaranteed to never be called
// concurrently according to z_info_routers_zid docstring
- static_cast(id);
(*(static_cast(ctx)))++;
};
@@ -39,14 +44,15 @@ rmw_ret_t zenoh_router_check(z_session_t session)
z_owned_closure_zid_t router_callback = z_closure(callback, nullptr /* drop */, &context);
if (z_info_routers_zid(session, z_move(router_callback))) {
RCUTILS_LOG_ERROR_NAMED(
- "ZenohRouterCheck",
- "Failed to evaluate if Zenoh routers are connected to the session");
+ "rmw_zenoh_cpp",
+ "Failed to evaluate if Zenoh routers are connected to the session.");
ret = RMW_RET_ERROR;
} else {
if (context == 0) {
RCUTILS_LOG_ERROR_NAMED(
- "ZenohRouterCheck",
- "No Zenoh router connected to the session");
+ "rmw_zenoh_cpp",
+ "Unable to connect to a Zenoh router. "
+ "Have you started a router with `ros2 run rmw_zenoh_cpp rmw_zenohd`?");
ret = RMW_RET_ERROR;
}
}
diff --git a/rmw_zenoh_cpp/src/rmw_init.cpp b/rmw_zenoh_cpp/src/rmw_init.cpp
index b90cdf61..edd95455 100644
--- a/rmw_zenoh_cpp/src/rmw_init.cpp
+++ b/rmw_zenoh_cpp/src/rmw_init.cpp
@@ -16,6 +16,7 @@
#include
#include
+#include
#include "detail/guard_condition.hpp"
#include "detail/identifier.hpp"
@@ -175,10 +176,24 @@ rmw_init(const rmw_init_options_t * options, rmw_context_t * context)
z_id_t zid = z_info_zid(z_loan(context->impl->session));
context->impl->graph_cache = std::make_unique(zid);
- // Verify if the zenoh router is running.
- if ((ret = zenoh_router_check(z_loan(context->impl->session))) != RMW_RET_OK) {
- RMW_SET_ERROR_MSG("Error while checking for Zenoh router");
- return ret;
+ // Verify if the zenoh router is running if configured.
+ const std::optional configured_connection_attempts = zenoh_router_check_attempts();
+ if (configured_connection_attempts.has_value()) {
+ ret = RMW_RET_ERROR;
+ uint64_t connection_attempts = 0;
+ // Retry until the connection is successful.
+ while (ret != RMW_RET_OK && connection_attempts < configured_connection_attempts.value()) {
+ if ((ret = zenoh_router_check(z_loan(context->impl->session))) != RMW_RET_OK) {
+ ++connection_attempts;
+ }
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ }
+ if (ret != RMW_RET_OK) {
+ RMW_SET_ERROR_MSG_WITH_FORMAT_STRING(
+ "Unable to connect to a Zenoh router after %zu retries.",
+ configured_connection_attempts.value());
+ return ret;
+ }
}
// Initialize the shm manager if shared_memory is enabled in the config.
@@ -362,15 +377,6 @@ rmw_shutdown(rmw_context_t * context)
return RMW_RET_ERROR;
}
- const rcutils_allocator_t * allocator = &context->options.allocator;
-
- RMW_TRY_DESTRUCTOR(
- static_cast(context->impl->graph_guard_condition->data)->~GuardCondition(),
- GuardCondition, );
- allocator->deallocate(context->impl->graph_guard_condition->data, allocator->state);
-
- allocator->deallocate(context->impl->graph_guard_condition, allocator->state);
-
context->impl->is_shutdown = true;
return RMW_RET_OK;
@@ -396,10 +402,18 @@ rmw_context_fini(rmw_context_t * context)
return RMW_RET_INVALID_ARGUMENT;
}
- RMW_TRY_DESTRUCTOR(context->impl->~rmw_context_impl_t(), rmw_context_impl_t, );
-
const rcutils_allocator_t * allocator = &context->options.allocator;
+ RMW_TRY_DESTRUCTOR(
+ static_cast(context->impl->graph_guard_condition->data)->~GuardCondition(),
+ GuardCondition, );
+ allocator->deallocate(context->impl->graph_guard_condition->data, allocator->state);
+
+ allocator->deallocate(context->impl->graph_guard_condition, allocator->state);
+ context->impl->graph_guard_condition = nullptr;
+
+ RMW_TRY_DESTRUCTOR(context->impl->~rmw_context_impl_t(), rmw_context_impl_t, );
+
allocator->deallocate(context->impl, allocator->state);
rmw_ret_t ret = rmw_init_options_fini(&context->options);
diff --git a/rmw_zenoh_cpp/src/rmw_zenoh.cpp b/rmw_zenoh_cpp/src/rmw_zenoh.cpp
index 802a1269..08c90b87 100644
--- a/rmw_zenoh_cpp/src/rmw_zenoh.cpp
+++ b/rmw_zenoh_cpp/src/rmw_zenoh.cpp
@@ -2277,7 +2277,7 @@ rmw_send_request(
opts.target = Z_QUERY_TARGET_ALL_COMPLETE;
// The default timeout for a z_get query is 10 seconds and if a response is not received within
// this window, the queryable will return an invalid reply. However, it is common for actions,
- // which are implemented using services, to take an extended duration to complete.Hence, we set
+ // which are implemented using services, to take an extended duration to complete. Hence, we set
// the timeout_ms to the largest supported value to account for most realistic scenarios.
opts.timeout_ms = std::numeric_limits::max();
// Latest consolidation guarantees unicity of replies for the same key expression,