diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/data/ros2_topic_data_provider.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/data/ros2_topic_data_provider.hpp index 70e3e3df5..5da82fd84 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/data/ros2_topic_data_provider.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/data/ros2_topic_data_provider.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -93,6 +94,7 @@ class Ros2TopicDataProvider final : public TopicDataProvider { std::size_t graph_events_received; std::size_t concurrent_cold_waits; std::size_t cold_wait_cap; + std::size_t unsupported_type_count; }; Ros2TopicDataProvider(std::shared_ptr exec, @@ -166,6 +168,14 @@ class Ros2TopicDataProvider final : public TopicDataProvider { std::shared_ptr exec_; std::shared_ptr serializer_; + // Message types whose package is not installed: warned once, then skipped on + // subsequent samples instead of re-attempting deserialize per message. + // Bounded by kMaxUnsupportedTypes so a stack advertising many distinct + // unknown types cannot grow the cache without limit. + static constexpr std::size_t kMaxUnsupportedTypes = 4096; + mutable std::mutex unsupported_types_mtx_; + std::unordered_set unsupported_types_; + std::atomic shutdown_{false}; // Shared alive flag for graph callback: registered with the executor so the diff --git a/src/ros2_medkit_gateway/src/data/ros2_topic_data_provider.cpp b/src/ros2_medkit_gateway/src/data/ros2_topic_data_provider.cpp index 40197df15..ff5dce80e 100644 --- a/src/ros2_medkit_gateway/src/data/ros2_topic_data_provider.cpp +++ b/src/ros2_medkit_gateway/src/data/ros2_topic_data_provider.cpp @@ -486,6 +486,13 @@ tl::expected Ros2TopicDataProvider::sample(const s result.has_data = false; return result; } + { + std::lock_guard lk(unsupported_types_mtx_); + if (unsupported_types_.count(type_name) != 0) { + result.has_data = false; + return result; + } + } try { result.data = serializer_->deserialize(type_name, serialized_copy); result.has_data = true; @@ -493,8 +500,20 @@ tl::expected Ros2TopicDataProvider::sample(const s result.timestamp_ns = msg_ns; } } catch (const ros2_medkit_serialization::TypeNotFoundError & e) { - RCLCPP_WARN(exec_->node()->get_logger(), "Unknown type '%s' for topic '%s': %s", type_name.c_str(), topic.c_str(), - e.what()); + bool first_seen = false; + { + std::lock_guard lk(unsupported_types_mtx_); + // Cap the cache: past the bound stop tracking (and stop warning) rather + // than grow memory without limit on a graph with many unknown types. + if (unsupported_types_.size() < kMaxUnsupportedTypes) { + first_seen = unsupported_types_.insert(type_name).second; + } + } + if (first_seen) { + RCLCPP_WARN(exec_->node()->get_logger(), + "Unknown type '%s' for topic '%s': %s (skipping further samples of this type)", type_name.c_str(), + topic.c_str(), e.what()); + } result.has_data = false; } catch (const ros2_medkit_serialization::SerializationError & e) { RCLCPP_WARN(exec_->node()->get_logger(), "Deserialize failed on '%s': %s", topic.c_str(), e.what()); @@ -933,6 +952,7 @@ nlohmann::json Ros2TopicDataProvider::x_medkit_stats() const { {"graph_events_received", p.graph_events_received}, {"concurrent_cold_waits", p.concurrent_cold_waits}, {"cold_wait_cap", p.cold_wait_cap}, + {"unsupported_type_count", p.unsupported_type_count}, }; if (exec_) { const auto e = exec_->stats(); @@ -968,6 +988,10 @@ Ros2TopicDataProvider::PoolStats Ros2TopicDataProvider::stats() const { s.graph_events_received = graph_events_received_.load(); s.concurrent_cold_waits = concurrent_cold_waits_.load(); s.cold_wait_cap = cfg_.cold_wait_cap; + { + std::lock_guard lk(unsupported_types_mtx_); + s.unsupported_type_count = unsupported_types_.size(); + } return s; } diff --git a/src/ros2_medkit_gateway/test/test_ros2_topic_data_provider.cpp b/src/ros2_medkit_gateway/test/test_ros2_topic_data_provider.cpp index f6ae84149..d9629bf34 100644 --- a/src/ros2_medkit_gateway/test/test_ros2_topic_data_provider.cpp +++ b/src/ros2_medkit_gateway/test/test_ros2_topic_data_provider.cpp @@ -99,6 +99,7 @@ TEST_F(Ros2TopicDataProviderTest, ConstructedProviderHasEmptyStats) { EXPECT_EQ(s.pool_hits, 0u); EXPECT_EQ(s.pool_misses, 0u); EXPECT_GT(s.pool_cap, 0u); + EXPECT_EQ(s.unsupported_type_count, 0u); } // @verifies REQ_INTEROP_018