From 849b47e4185a96cec606736711a348bd9173e33f Mon Sep 17 00:00:00 2001 From: CY Chen Date: Mon, 16 Mar 2026 09:50:47 +0000 Subject: [PATCH 1/9] Add rosidl_buffer_backend_registry Signed-off-by: CY Chen --- rosidl_buffer_backend_registry/CMakeLists.txt | 89 ++++++ .../buffer_backend_loader.hpp | 38 +++ .../buffer_backend_registry.hpp | 132 +++++++++ .../visibility_control.h | 58 ++++ rosidl_buffer_backend_registry/package.xml | 29 ++ .../src/buffer_backend_loader.cpp | 105 +++++++ .../src/buffer_backend_registry.cpp | 257 ++++++++++++++++++ .../test/dummy_buffer_backend.hpp | 125 +++++++++ .../test/test_buffer_backend_registry.cpp | 102 +++++++ 9 files changed, 935 insertions(+) create mode 100644 rosidl_buffer_backend_registry/CMakeLists.txt create mode 100644 rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp create mode 100644 rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp create mode 100644 rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/visibility_control.h create mode 100644 rosidl_buffer_backend_registry/package.xml create mode 100644 rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp create mode 100644 rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp create mode 100644 rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp create mode 100644 rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp diff --git a/rosidl_buffer_backend_registry/CMakeLists.txt b/rosidl_buffer_backend_registry/CMakeLists.txt new file mode 100644 index 000000000..2fb9deff4 --- /dev/null +++ b/rosidl_buffer_backend_registry/CMakeLists.txt @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 3.5) +project(rosidl_buffer_backend_registry) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# Find dependencies +find_package(ament_cmake REQUIRED) +find_package(rosidl_buffer_backend REQUIRED) +find_package(rmw REQUIRED) +find_package(rosidl_runtime_cpp REQUIRED) +find_package(rosidl_typesupport_fastrtps_cpp REQUIRED) +find_package(pluginlib REQUIRED) + +# Library +add_library(${PROJECT_NAME} SHARED + src/buffer_backend_loader.cpp + src/buffer_backend_registry.cpp +) + +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) + +target_compile_definitions(${PROJECT_NAME} + PRIVATE "ROSIDL_BUFFER_BACKEND_REGISTRY_BUILDING_DLL" +) + +target_link_libraries(${PROJECT_NAME} PUBLIC + rosidl_buffer_backend::rosidl_buffer_backend + rmw::rmw + rosidl_runtime_cpp::rosidl_runtime_cpp + rosidl_typesupport_fastrtps_cpp::rosidl_typesupport_fastrtps_cpp + pluginlib::pluginlib +) + +# Install headers +install( + DIRECTORY include/ + DESTINATION include/${PROJECT_NAME} +) + +# Install library +install( + TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include/${PROJECT_NAME} +) + +# Export dependencies +ament_export_targets(${PROJECT_NAME} HAS_LIBRARY_TARGET) +ament_export_dependencies(rosidl_buffer_backend rmw rosidl_runtime_cpp rosidl_typesupport_fastrtps_cpp pluginlib) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + + find_package(ament_cmake_gtest REQUIRED) + + # Test backend registry and dummy backend (direct instantiation, not via pluginlib) + ament_add_gtest(test_buffer_backend_registry + test/test_buffer_backend_registry.cpp + ) + if(TARGET test_buffer_backend_registry) + target_include_directories(test_buffer_backend_registry PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/test + ) + target_link_libraries(test_buffer_backend_registry + ${PROJECT_NAME} + rosidl_buffer_backend::rosidl_buffer_backend + rmw::rmw + rosidl_runtime_cpp::rosidl_runtime_cpp + pluginlib::pluginlib + ) + endif() +endif() + +ament_package() + diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp new file mode 100644 index 000000000..0ca1e74ea --- /dev/null +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp @@ -0,0 +1,38 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_LOADER_HPP_ +#define ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_LOADER_HPP_ + +#include "rosidl_buffer_backend_registry/visibility_control.h" + +namespace rosidl_buffer_backend_registry +{ + +/// Load buffer backend plugins and register them with FastCDR serialization. +/// Populates the global BackendDescriptorOps and DescriptorSerializers maps +/// in rosidl_typesupport_fastrtps_cpp so that buffer-aware message types can +/// serialize/deserialize vendor-specific descriptors. +ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC +void initialize_buffer_backends(); + +/// Clear global serialization maps to release plugin references before unloading. +/// Must be called before BufferBackendRegistry singleton is destroyed to prevent +/// ClassLoader from trying to unload while objects still exist. +ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC +void shutdown_buffer_backends(); + +} // namespace rosidl_buffer_backend_registry + +#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_LOADER_HPP_ diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp new file mode 100644 index 000000000..13b119395 --- /dev/null +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp @@ -0,0 +1,132 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ +#define ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "rosidl_buffer_backend/buffer_backend.hpp" +#include "rosidl_buffer_backend_registry/visibility_control.h" + +#include "rmw/topic_endpoint_info.h" + +// Forward declare pluginlib ClassLoader to avoid header dependency +namespace pluginlib +{ +template +class ClassLoader; +} // namespace pluginlib + +namespace rosidl_buffer_backend_registry +{ + +/// Singleton registry for discovering and managing buffer backend plugins. +/// Uses pluginlib for dynamic plugin discovery and loading. +class BufferBackendRegistry +{ +public: + /// Get the singleton instance. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + static BufferBackendRegistry & get_instance(); + + /// Get a registered backend by name (e.g., "cpu", "cuda"). + /// Returns nullptr if backend not found. + /// Thread-safe after initial load_plugins() call. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + std::shared_ptr get_backend(const std::string & name); + + /// Manually register a backend (for built-in backends or testing). + /// Thread-safe. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + void register_backend(const std::string & name, std::shared_ptr backend); + + /// Load all available backend plugins via pluginlib. + /// Called automatically on first get_backend() if not already loaded. + /// Thread-safe (uses call_once). + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + void load_plugins(); + + /// Get names of all registered backends. + /// Thread-safe after initial load_plugins() call. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + std::vector get_backend_names() const; + + /// Get backend type strings for all registered backends, always including "cpu". + /// Each backend's get_backend_type() is queried (e.g., "cuda", "rocm"). + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + std::vector get_backend_types(); + + /// Collect aux info from all registered backends. + /// @return Map of backend name to aux info string. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + std::unordered_map get_all_aux_info(); + + /// Notify all backends that a local endpoint is being created. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + void notify_endpoint_created(const rmw_topic_endpoint_info_t & endpoint_info); + + /// Notify all backends that a remote endpoint has been discovered. + /// @param endpoint_info Information about the discovered endpoint. + /// @param existing_endpoints List of existing endpoints for grouping decisions. + /// @param backend_endpoint_groups In/out parameter for backend-specific grouping information. + /// @param endpoint_supported_backends Backend aux info from the discovered endpoint. + /// @return Map of backend name to compatibility boolean. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + std::unordered_map notify_endpoint_discovered( + const rmw_topic_endpoint_info_t & endpoint_info, + const std::vector & existing_endpoints, + std::unordered_map>> & backend_endpoint_groups, + const std::unordered_map & endpoint_supported_backends); + + /// Check if two backend type lists have at least one common entry. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + static bool backends_compatible( + const std::vector & a, + const std::vector & b); + + /// Get the intersection of two backend type lists. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + static std::vector get_common_backends( + const std::vector & a, + const std::vector & b); + + /// Clear all global state including backends and serialization maps. + /// Called automatically in destructor to prevent plugin cleanup issues. + ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + void clear_global_state(); + + // Non-copyable, non-movable + BufferBackendRegistry(const BufferBackendRegistry &) = delete; + BufferBackendRegistry & operator=(const BufferBackendRegistry &) = delete; + BufferBackendRegistry(BufferBackendRegistry &&) = delete; + BufferBackendRegistry & operator=(BufferBackendRegistry &&) = delete; + +private: + BufferBackendRegistry(); + ~BufferBackendRegistry(); + + std::map> backends_; + std::unique_ptr> loader_; + bool plugins_loaded_; +}; + +} // namespace rosidl_buffer_backend_registry + +#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/visibility_control.h b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/visibility_control.h new file mode 100644 index 000000000..5c53f070d --- /dev/null +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/visibility_control.h @@ -0,0 +1,58 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__VISIBILITY_CONTROL_H_ +#define ROSIDL_BUFFER_BACKEND_REGISTRY__VISIBILITY_CONTROL_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef __GNUC__ + #define ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT __attribute__((dllexport)) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT __attribute__((dllimport)) + #else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT __declspec(dllexport) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT __declspec(dllimport) + #endif + #ifdef ROSIDL_BUFFER_BACKEND_REGISTRY_BUILDING_DLL + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT + #else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT + #endif + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC_TYPE ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + #define ROSIDL_BUFFER_BACKEND_REGISTRY_LOCAL +#else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_EXPORT __attribute__((visibility("default"))) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_IMPORT + #if __GNUC__ >= 4 + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC __attribute__((visibility("default"))) + #define ROSIDL_BUFFER_BACKEND_REGISTRY_LOCAL __attribute__((visibility("hidden"))) + #else + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC + #define ROSIDL_BUFFER_BACKEND_REGISTRY_LOCAL + #endif + #define ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__VISIBILITY_CONTROL_H_ diff --git a/rosidl_buffer_backend_registry/package.xml b/rosidl_buffer_backend_registry/package.xml new file mode 100644 index 000000000..e565dcd37 --- /dev/null +++ b/rosidl_buffer_backend_registry/package.xml @@ -0,0 +1,29 @@ + + + + rosidl_buffer_backend_registry + 5.1.2 + Backend discovery and plugin loading for ROS2 buffer types + + CY Chen + + Apache License 2.0 + + CY Chen + + ament_cmake + + rosidl_buffer_backend + rmw + rosidl_runtime_cpp + rosidl_typesupport_fastrtps_cpp + pluginlib + + ament_lint_auto + ament_lint_common + + + ament_cmake + + + diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp new file mode 100644 index 000000000..cfe11e928 --- /dev/null +++ b/rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp @@ -0,0 +1,105 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "rosidl_buffer_backend_registry/buffer_backend_loader.hpp" + +#include +#include + +#include "rcutils/logging_macros.h" +#include "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" +#include "rosidl_typesupport_fastrtps_cpp/buffer_serialization.hpp" + +namespace rosidl_buffer_backend_registry +{ + +static const char * kLoggerName = "rosidl_buffer_backend_registry"; + +void initialize_buffer_backends() +{ + auto & registry = BufferBackendRegistry::get_instance(); + registry.load_plugins(); + + auto & backend_ops = rosidl_typesupport_fastrtps_cpp::get_backend_descriptor_ops(); + auto backend_names = registry.get_backend_names(); + RCUTILS_LOG_INFO_NAMED( + kLoggerName, "Buffer backends: found %zu backend(s)", backend_names.size()); + + for (const auto & backend_name : backend_names) { + auto backend = registry.get_backend(backend_name); + if (!backend) { + RCUTILS_LOG_ERROR_NAMED( + kLoggerName, "Backend '%s' pointer is null", backend_name.c_str()); + continue; + } + + std::string backend_type = backend->get_backend_type(); + RCUTILS_LOG_INFO_NAMED( + kLoggerName, "Processing backend '%s' (type: %s)", + backend_name.c_str(), backend_type.c_str()); + + rosidl_typesupport_fastrtps_cpp::BufferDescriptorOps ops; + + auto backend_ptr = backend; + ops.create_descriptor_with_endpoint = [backend_ptr]( + const std::shared_ptr & impl, + const rmw_topic_endpoint_info_t & endpoint_info) -> std::shared_ptr { + return backend_ptr->create_descriptor_with_endpoint(impl, endpoint_info); + }; + ops.from_descriptor_with_endpoint = [backend_ptr]( + const std::shared_ptr & descriptor, + const rmw_topic_endpoint_info_t & endpoint_info) -> std::shared_ptr { + return backend_ptr->from_descriptor_with_endpoint(descriptor, endpoint_info); + }; + + backend_ops[backend_type] = ops; + + auto & serializers = rosidl_typesupport_fastrtps_cpp::get_descriptor_serializers(); + if (serializers.find(backend_type) != serializers.end()) { + RCUTILS_LOG_INFO_NAMED( + kLoggerName, " FastCDR descriptor serializers registered for '%s'", + backend_type.c_str()); + } else { + RCUTILS_LOG_ERROR_NAMED( + kLoggerName, + " Backend '%s' did not register FastCDR descriptor serializers. " + "Ensure the backend constructor calls " + "rosidl_typesupport_fastrtps_cpp::register_buffer_descriptor()", + backend_type.c_str()); + } + } +} + +void shutdown_buffer_backends() +{ + try { + auto & backend_ops = rosidl_typesupport_fastrtps_cpp::get_backend_descriptor_ops(); + backend_ops.clear(); + + auto & serializers = rosidl_typesupport_fastrtps_cpp::get_descriptor_serializers(); + serializers.clear(); + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + kLoggerName, "Warning during buffer backend shutdown: %s", e.what()); + } + + try { + BufferBackendRegistry::get_instance().clear_global_state(); + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + kLoggerName, "Warning clearing backend registry: %s", e.what()); + } +} + +} // namespace rosidl_buffer_backend_registry diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp new file mode 100644 index 000000000..9f55c659c --- /dev/null +++ b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp @@ -0,0 +1,257 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" + +#include +#include + +#include + +namespace rosidl_buffer_backend_registry +{ + +BufferBackendRegistry::BufferBackendRegistry() +: plugins_loaded_(false) +{ + try { + loader_ = std::make_unique>( + "rosidl_buffer_backend", + "rosidl::BufferBackend"); + } catch (const std::exception & e) { + // Silently ignore if pluginlib isn't available - will work without plugins + loader_ = nullptr; + } +} + +BufferBackendRegistry::~BufferBackendRegistry() +{ + // CRITICAL: Clear all global state before ClassLoader is destroyed + clear_global_state(); +} + +BufferBackendRegistry & BufferBackendRegistry::get_instance() +{ + static BufferBackendRegistry instance; + return instance; +} + +std::shared_ptr BufferBackendRegistry::get_backend(const std::string & name) +{ + // Ensure plugins are loaded on first access + if (!plugins_loaded_) { + load_plugins(); + } + + auto it = backends_.find(name); + if (it != backends_.end()) { + return it->second; + } + + return nullptr; +} + +void BufferBackendRegistry::register_backend( + const std::string & name, + std::shared_ptr backend) +{ + static std::mutex mutex; + std::lock_guard lock(mutex); + backends_[name] = backend; +} + +void BufferBackendRegistry::load_plugins() +{ + static std::once_flag load_flag; + std::call_once( + load_flag, [this]() { + if (!loader_) { + plugins_loaded_ = true; + return; + } + + try { + auto declared_classes = loader_->getDeclaredClasses(); + if (declared_classes.empty()) { + RCUTILS_LOG_INFO_NAMED( + "rosidl_buffer_backend_registry", + "No buffer backend plugins found"); + } else { + RCUTILS_LOG_INFO_NAMED( + "rosidl_buffer_backend_registry", + "Discovered %zu buffer backend plugin(s)", + declared_classes.size()); + for (const auto & class_name : declared_classes) { + try { + auto backend = loader_->createSharedInstance(class_name); + register_backend(class_name, backend); + RCUTILS_LOG_INFO_NAMED( + "rosidl_buffer_backend_registry", + "Loaded buffer backend plugin: %s", + class_name.c_str()); + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Failed to load %s: %s", + class_name.c_str(), e.what()); + continue; + } + } + } + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Buffer backend plugin discovery error: %s", + e.what()); + } + + plugins_loaded_ = true; + }); +} + +std::vector BufferBackendRegistry::get_backend_names() const +{ + std::vector names; + names.reserve(backends_.size()); + for (const auto & pair : backends_) { + names.push_back(pair.first); + } + return names; +} + +std::vector BufferBackendRegistry::get_backend_types() +{ + // Ensure plugins are loaded + if (!plugins_loaded_) { + load_plugins(); + } + + std::vector backend_types; + + // Always include CPU backend + backend_types.push_back("cpu"); + + // Get additional backends from loaded plugins + for (const auto & pair : backends_) { + auto & backend = pair.second; + if (backend) { + std::string backend_type = backend->get_backend_type(); + // Avoid duplicating CPU if it's in the registry + if (backend_type != "cpu") { + backend_types.push_back(backend_type); + } + } + } + + return backend_types; +} + +std::unordered_map BufferBackendRegistry::get_all_aux_info() +{ + // Ensure plugins are loaded + if (!plugins_loaded_) { + load_plugins(); + } + + std::unordered_map aux_info; + for (const auto & pair : backends_) { + if (pair.second) { + aux_info[pair.first] = pair.second->get_backend_aux_info(); + } + } + return aux_info; +} + +void BufferBackendRegistry::notify_endpoint_created( + const rmw_topic_endpoint_info_t & endpoint_info) +{ + for (const auto & pair : backends_) { + if (pair.second) { + pair.second->on_creating_endpoint(endpoint_info); + } + } +} + +std::unordered_map BufferBackendRegistry::notify_endpoint_discovered( + const rmw_topic_endpoint_info_t & endpoint_info, + const std::vector & existing_endpoints, + std::unordered_map>> & backend_endpoint_groups, + const std::unordered_map & endpoint_supported_backends) +{ + std::unordered_map backend_compatibility; + for (const auto & pair : backends_) { + const auto & backend_name = pair.first; + const auto & backend = pair.second; + if (!backend) { + backend_compatibility[backend_name] = false; + backend_endpoint_groups[backend_name] = {}; + continue; + } + auto result = backend->on_discovering_endpoint( + endpoint_info, existing_endpoints, endpoint_supported_backends); + backend_compatibility[backend_name] = result.first; + backend_endpoint_groups[backend_name] = std::move(result.second); + } + return backend_compatibility; +} + +bool BufferBackendRegistry::backends_compatible( + const std::vector & a, + const std::vector & b) +{ + for (const auto & backend_a : a) { + for (const auto & backend_b : b) { + if (backend_a == backend_b) { + return true; + } + } + } + return false; +} + +std::vector BufferBackendRegistry::get_common_backends( + const std::vector & a, + const std::vector & b) +{ + std::vector common; + for (const auto & backend_a : a) { + for (const auto & backend_b : b) { + if (backend_a == backend_b) { + bool already_added = false; + for (const auto & c : common) { + if (c == backend_a) { + already_added = true; + break; + } + } + if (!already_added) { + common.push_back(backend_a); + } + } + } + } + return common; +} + +void BufferBackendRegistry::clear_global_state() +{ + // CRITICAL: Clear backends_ map to release shared_ptr to plugin instances + // before ClassLoader is destroyed. + backends_.clear(); + + // Note: Global serialization maps in rosidl_typesupport_fastrtps_cpp are cleared + // by rmw_shutdown() which is called before this destructor runs. +} + +} // namespace rosidl_buffer_backend_registry diff --git a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp new file mode 100644 index 000000000..97d0b1a86 --- /dev/null +++ b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp @@ -0,0 +1,125 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DUMMY_BUFFER_BACKEND_HPP_ +#define DUMMY_BUFFER_BACKEND_HPP_ + +#include +#include +#include +#include + +#include "rosidl_buffer_backend/buffer_backend.hpp" +#include "rosidl_buffer/buffer_impl_base.hpp" +#include "rosidl_buffer/cpu_buffer_impl.hpp" + +// For pluginlib export +#include + +namespace rosidl_buffer_backend_registry +{ +namespace test +{ + +/// Dummy backend implementation for testing. +/// Acts like CPU backend but adds a marker to verify it's being used. +template +class DummyBufferImpl : public rosidl::BufferImplBase +{ +public: + DummyBufferImpl() + : marker_(0xDEADBEEF) {} + + explicit DummyBufferImpl(size_t size) + : marker_(0xDEADBEEF) + { + data_.resize(size); + } + + std::string get_backend_type() const override {return "dummy";} + + size_t size() const override {return data_.size();} + + std::unique_ptr> to_cpu() const override + { + auto cpu = std::make_unique>(); + cpu->get_storage() = data_; + return cpu; + } + + std::unique_ptr> clone() const override + { + auto cloned = std::make_unique>(); + cloned->data_ = data_; + cloned->marker_ = marker_; + return cloned; + } + + std::vector & get_data() {return data_;} + const std::vector & get_data() const {return data_;} + + uint32_t get_marker() const {return marker_;} + +private: + std::vector data_; + uint32_t marker_; // Marker to identify this backend +}; + +/// Dummy backend for testing the BufferBackend interface. +/// Implements all 4+4 methods with test markers. +class DummyBufferBackend : public rosidl::BufferBackend +{ +public: + DummyBufferBackend() + : call_count_(0) {} + + // Track which methods were called + mutable size_t call_count_; + mutable std::string last_method_; + + std::string get_backend_type() const override + { + return "dummy"; + } + + std::shared_ptr create_descriptor_with_endpoint( + const std::shared_ptr & impl, + const rmw_topic_endpoint_info_t & endpoint_info) const override + { + // Return nullptr - not used in these tests + (void)impl; + (void)endpoint_info; + return nullptr; + } + + std::shared_ptr from_descriptor_with_endpoint( + const std::shared_ptr & descriptor, + const rmw_topic_endpoint_info_t & endpoint_info) const override + { + // Return nullptr - not used in these tests + (void)descriptor; + (void)endpoint_info; + return nullptr; + } +}; + +} // namespace test +} // namespace rosidl_buffer_backend_registry + +// Export the DummyBufferBackend as a pluginlib plugin +PLUGINLIB_EXPORT_CLASS( + rosidl_buffer_backend_registry::test::DummyBufferBackend, + rosidl::BufferBackend) + +#endif // DUMMY_BUFFER_BACKEND_HPP_ diff --git a/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp new file mode 100644 index 000000000..14353dd2e --- /dev/null +++ b/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp @@ -0,0 +1,102 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +#include "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" +#include "dummy_buffer_backend.hpp" + +using rosidl_buffer_backend_registry::BufferBackendRegistry; +using rosidl_buffer_backend_registry::test::DummyBufferBackend; +using rosidl_buffer_backend_registry::test::DummyBufferImpl; + +// Test getting singleton instance +TEST(TestBufferBackendRegistry, get_instance) { + auto & registry1 = BufferBackendRegistry::get_instance(); + auto & registry2 = BufferBackendRegistry::get_instance(); + + // Should be the same instance + EXPECT_EQ(®istry1, ®istry2); +} + +// Test registering and getting a backend +TEST(TestBufferBackendRegistry, register_and_get_backend) { + auto & registry = BufferBackendRegistry::get_instance(); + + auto dummy_backend = std::make_shared(); + registry.register_backend("dummy_test", dummy_backend); + + auto retrieved = registry.get_backend("dummy_test"); + ASSERT_NE(nullptr, retrieved); + EXPECT_EQ(dummy_backend.get(), retrieved.get()); +} + +// Test getting non-existent backend +TEST(TestBufferBackendRegistry, get_nonexistent_backend) { + auto & registry = BufferBackendRegistry::get_instance(); + + auto backend = registry.get_backend("nonexistent_backend_12345"); + EXPECT_EQ(nullptr, backend); +} + +// Test DummyBufferImpl to_cpu() conversion +TEST(TestDummyBufferImpl, to_cpu_conversion) { + DummyBufferImpl dummy(4); + dummy.get_data()[0] = 10; + dummy.get_data()[1] = 20; + dummy.get_data()[2] = 30; + dummy.get_data()[3] = 40; + + auto cpu_impl = dummy.to_cpu(); + ASSERT_NE(nullptr, cpu_impl); + + auto * cpu = static_cast *>(cpu_impl.get()); + EXPECT_EQ(4u, cpu->get_storage().size()); + EXPECT_EQ(10, cpu->get_storage()[0]); + EXPECT_EQ(20, cpu->get_storage()[1]); + EXPECT_EQ(30, cpu->get_storage()[2]); + EXPECT_EQ(40, cpu->get_storage()[3]); +} + +// Test marker preservation +TEST(TestDummyBufferImpl, marker_verification) { + DummyBufferImpl impl1; + DummyBufferImpl impl2; + + EXPECT_EQ(0xDEADBEEFu, impl1.get_marker()); + EXPECT_EQ(0xDEADBEEFu, impl2.get_marker()); +} + +// Test data access via get_data() +TEST(TestDummyBufferImpl, data_access) { + DummyBufferImpl impl; + + EXPECT_TRUE(impl.get_data().empty()); + + impl.get_data().resize(3); + impl.get_data()[0] = 100; + + const int * ptr = impl.get_data().data(); + ASSERT_NE(nullptr, ptr); + EXPECT_EQ(100, ptr[0]); +} + +int main(int argc, char ** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From a45f4d74a12dddc454d53f543911ba58f2c7407a Mon Sep 17 00:00:00 2001 From: CY Chen Date: Thu, 19 Mar 2026 06:00:35 +0000 Subject: [PATCH 2/9] Move buffer backend init/shutdown utility functions to RMW layer Signed-off-by: CY Chen --- rosidl_buffer_backend_registry/CMakeLists.txt | 5 +- .../buffer_backend_loader.hpp | 38 ------- rosidl_buffer_backend_registry/package.xml | 1 - .../src/buffer_backend_loader.cpp | 105 ------------------ 4 files changed, 1 insertion(+), 148 deletions(-) delete mode 100644 rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp delete mode 100644 rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp diff --git a/rosidl_buffer_backend_registry/CMakeLists.txt b/rosidl_buffer_backend_registry/CMakeLists.txt index 2fb9deff4..2c6eb93e6 100644 --- a/rosidl_buffer_backend_registry/CMakeLists.txt +++ b/rosidl_buffer_backend_registry/CMakeLists.txt @@ -15,12 +15,10 @@ find_package(ament_cmake REQUIRED) find_package(rosidl_buffer_backend REQUIRED) find_package(rmw REQUIRED) find_package(rosidl_runtime_cpp REQUIRED) -find_package(rosidl_typesupport_fastrtps_cpp REQUIRED) find_package(pluginlib REQUIRED) # Library add_library(${PROJECT_NAME} SHARED - src/buffer_backend_loader.cpp src/buffer_backend_registry.cpp ) @@ -37,7 +35,6 @@ target_link_libraries(${PROJECT_NAME} PUBLIC rosidl_buffer_backend::rosidl_buffer_backend rmw::rmw rosidl_runtime_cpp::rosidl_runtime_cpp - rosidl_typesupport_fastrtps_cpp::rosidl_typesupport_fastrtps_cpp pluginlib::pluginlib ) @@ -59,7 +56,7 @@ install( # Export dependencies ament_export_targets(${PROJECT_NAME} HAS_LIBRARY_TARGET) -ament_export_dependencies(rosidl_buffer_backend rmw rosidl_runtime_cpp rosidl_typesupport_fastrtps_cpp pluginlib) +ament_export_dependencies(rosidl_buffer_backend rmw rosidl_runtime_cpp pluginlib) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp deleted file mode 100644 index 0ca1e74ea..000000000 --- a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_loader.hpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2026 Open Source Robotics Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_LOADER_HPP_ -#define ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_LOADER_HPP_ - -#include "rosidl_buffer_backend_registry/visibility_control.h" - -namespace rosidl_buffer_backend_registry -{ - -/// Load buffer backend plugins and register them with FastCDR serialization. -/// Populates the global BackendDescriptorOps and DescriptorSerializers maps -/// in rosidl_typesupport_fastrtps_cpp so that buffer-aware message types can -/// serialize/deserialize vendor-specific descriptors. -ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC -void initialize_buffer_backends(); - -/// Clear global serialization maps to release plugin references before unloading. -/// Must be called before BufferBackendRegistry singleton is destroyed to prevent -/// ClassLoader from trying to unload while objects still exist. -ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC -void shutdown_buffer_backends(); - -} // namespace rosidl_buffer_backend_registry - -#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_LOADER_HPP_ diff --git a/rosidl_buffer_backend_registry/package.xml b/rosidl_buffer_backend_registry/package.xml index e565dcd37..1882d2dee 100644 --- a/rosidl_buffer_backend_registry/package.xml +++ b/rosidl_buffer_backend_registry/package.xml @@ -16,7 +16,6 @@ rosidl_buffer_backend rmw rosidl_runtime_cpp - rosidl_typesupport_fastrtps_cpp pluginlib ament_lint_auto diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp deleted file mode 100644 index cfe11e928..000000000 --- a/rosidl_buffer_backend_registry/src/buffer_backend_loader.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2026 Open Source Robotics Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "rosidl_buffer_backend_registry/buffer_backend_loader.hpp" - -#include -#include - -#include "rcutils/logging_macros.h" -#include "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" -#include "rosidl_typesupport_fastrtps_cpp/buffer_serialization.hpp" - -namespace rosidl_buffer_backend_registry -{ - -static const char * kLoggerName = "rosidl_buffer_backend_registry"; - -void initialize_buffer_backends() -{ - auto & registry = BufferBackendRegistry::get_instance(); - registry.load_plugins(); - - auto & backend_ops = rosidl_typesupport_fastrtps_cpp::get_backend_descriptor_ops(); - auto backend_names = registry.get_backend_names(); - RCUTILS_LOG_INFO_NAMED( - kLoggerName, "Buffer backends: found %zu backend(s)", backend_names.size()); - - for (const auto & backend_name : backend_names) { - auto backend = registry.get_backend(backend_name); - if (!backend) { - RCUTILS_LOG_ERROR_NAMED( - kLoggerName, "Backend '%s' pointer is null", backend_name.c_str()); - continue; - } - - std::string backend_type = backend->get_backend_type(); - RCUTILS_LOG_INFO_NAMED( - kLoggerName, "Processing backend '%s' (type: %s)", - backend_name.c_str(), backend_type.c_str()); - - rosidl_typesupport_fastrtps_cpp::BufferDescriptorOps ops; - - auto backend_ptr = backend; - ops.create_descriptor_with_endpoint = [backend_ptr]( - const std::shared_ptr & impl, - const rmw_topic_endpoint_info_t & endpoint_info) -> std::shared_ptr { - return backend_ptr->create_descriptor_with_endpoint(impl, endpoint_info); - }; - ops.from_descriptor_with_endpoint = [backend_ptr]( - const std::shared_ptr & descriptor, - const rmw_topic_endpoint_info_t & endpoint_info) -> std::shared_ptr { - return backend_ptr->from_descriptor_with_endpoint(descriptor, endpoint_info); - }; - - backend_ops[backend_type] = ops; - - auto & serializers = rosidl_typesupport_fastrtps_cpp::get_descriptor_serializers(); - if (serializers.find(backend_type) != serializers.end()) { - RCUTILS_LOG_INFO_NAMED( - kLoggerName, " FastCDR descriptor serializers registered for '%s'", - backend_type.c_str()); - } else { - RCUTILS_LOG_ERROR_NAMED( - kLoggerName, - " Backend '%s' did not register FastCDR descriptor serializers. " - "Ensure the backend constructor calls " - "rosidl_typesupport_fastrtps_cpp::register_buffer_descriptor()", - backend_type.c_str()); - } - } -} - -void shutdown_buffer_backends() -{ - try { - auto & backend_ops = rosidl_typesupport_fastrtps_cpp::get_backend_descriptor_ops(); - backend_ops.clear(); - - auto & serializers = rosidl_typesupport_fastrtps_cpp::get_descriptor_serializers(); - serializers.clear(); - } catch (const std::exception & e) { - RCUTILS_LOG_ERROR_NAMED( - kLoggerName, "Warning during buffer backend shutdown: %s", e.what()); - } - - try { - BufferBackendRegistry::get_instance().clear_global_state(); - } catch (const std::exception & e) { - RCUTILS_LOG_ERROR_NAMED( - kLoggerName, "Warning clearing backend registry: %s", e.what()); - } -} - -} // namespace rosidl_buffer_backend_registry From 8003dcd6815f03bcf8b41385d0b6d8c29a3c91a3 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Fri, 20 Mar 2026 07:01:58 +0000 Subject: [PATCH 3/9] Optimize code to address comments Signed-off-by: CY Chen --- .../buffer_backend_registry.hpp | 18 +++-- .../src/buffer_backend_registry.cpp | 76 +++++++------------ 2 files changed, 37 insertions(+), 57 deletions(-) diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp index 13b119395..ce7022bdb 100644 --- a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp @@ -39,6 +39,12 @@ namespace rosidl_buffer_backend_registry /// Singleton registry for discovering and managing buffer backend plugins. /// Uses pluginlib for dynamic plugin discovery and loading. +/// +/// Thread-safety model: load_plugins() is guarded by std::call_once and +/// register_backend() is mutex-protected, so initialization is thread-safe. +/// After load_plugins() completes, the backends map is effectively immutable +/// and all read-only accessors (get_backend, get_backend_names, etc.) are +/// safe for concurrent use without additional synchronization. class BufferBackendRegistry { public: @@ -47,24 +53,22 @@ class BufferBackendRegistry static BufferBackendRegistry & get_instance(); /// Get a registered backend by name (e.g., "cpu", "cuda"). - /// Returns nullptr if backend not found. - /// Thread-safe after initial load_plugins() call. + /// @return The backend, or nullptr if not found. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC std::shared_ptr get_backend(const std::string & name); /// Manually register a backend (for built-in backends or testing). - /// Thread-safe. + /// Thread-safe (mutex-protected). ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC void register_backend(const std::string & name, std::shared_ptr backend); /// Load all available backend plugins via pluginlib. /// Called automatically on first get_backend() if not already loaded. - /// Thread-safe (uses call_once). + /// Thread-safe (guarded by std::call_once). ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC void load_plugins(); /// Get names of all registered backends. - /// Thread-safe after initial load_plugins() call. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC std::vector get_backend_names() const; @@ -107,8 +111,8 @@ class BufferBackendRegistry const std::vector & a, const std::vector & b); - /// Clear all global state including backends and serialization maps. - /// Called automatically in destructor to prevent plugin cleanup issues. + /// Clear all backends and release plugin instances. + /// Called automatically in destructor before ClassLoader is destroyed. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC void clear_global_state(); diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp index 9f55c659c..fd79ed769 100644 --- a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp +++ b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp @@ -14,6 +14,7 @@ #include "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" +#include #include #include @@ -124,8 +125,8 @@ std::vector BufferBackendRegistry::get_backend_names() const { std::vector names; names.reserve(backends_.size()); - for (const auto & pair : backends_) { - names.push_back(pair.first); + for (const auto & [name, _] : backends_) { + names.push_back(name); } return names; } @@ -137,24 +138,17 @@ std::vector BufferBackendRegistry::get_backend_types() load_plugins(); } - std::vector backend_types; + std::set types_set; + // CPU is always implicitly available (Buffer defaults to CpuBufferImpl) + types_set.insert("cpu"); - // Always include CPU backend - backend_types.push_back("cpu"); - - // Get additional backends from loaded plugins - for (const auto & pair : backends_) { - auto & backend = pair.second; + for (const auto & [_, backend] : backends_) { if (backend) { - std::string backend_type = backend->get_backend_type(); - // Avoid duplicating CPU if it's in the registry - if (backend_type != "cpu") { - backend_types.push_back(backend_type); - } + types_set.insert(backend->get_backend_type()); } } - return backend_types; + return {types_set.begin(), types_set.end()}; } std::unordered_map BufferBackendRegistry::get_all_aux_info() @@ -165,9 +159,9 @@ std::unordered_map BufferBackendRegistry::get_all_aux_ } std::unordered_map aux_info; - for (const auto & pair : backends_) { - if (pair.second) { - aux_info[pair.first] = pair.second->get_backend_aux_info(); + for (const auto & [name, backend] : backends_) { + if (backend) { + aux_info[name] = backend->get_backend_aux_info(); } } return aux_info; @@ -176,9 +170,9 @@ std::unordered_map BufferBackendRegistry::get_all_aux_ void BufferBackendRegistry::notify_endpoint_created( const rmw_topic_endpoint_info_t & endpoint_info) { - for (const auto & pair : backends_) { - if (pair.second) { - pair.second->on_creating_endpoint(endpoint_info); + for (const auto & [_, backend] : backends_) { + if (backend) { + backend->on_creating_endpoint(endpoint_info); } } } @@ -190,9 +184,7 @@ std::unordered_map BufferBackendRegistry::notify_endpoint_dis const std::unordered_map & endpoint_supported_backends) { std::unordered_map backend_compatibility; - for (const auto & pair : backends_) { - const auto & backend_name = pair.first; - const auto & backend = pair.second; + for (const auto & [backend_name, backend] : backends_) { if (!backend) { backend_compatibility[backend_name] = false; backend_endpoint_groups[backend_name] = {}; @@ -210,14 +202,10 @@ bool BufferBackendRegistry::backends_compatible( const std::vector & a, const std::vector & b) { - for (const auto & backend_a : a) { - for (const auto & backend_b : b) { - if (backend_a == backend_b) { - return true; - } - } - } - return false; + return std::any_of( + a.begin(), a.end(), [&b](const std::string & entry) { + return std::find(b.begin(), b.end(), entry) != b.end(); + }); } std::vector BufferBackendRegistry::get_common_backends( @@ -225,20 +213,11 @@ std::vector BufferBackendRegistry::get_common_backends( const std::vector & b) { std::vector common; - for (const auto & backend_a : a) { - for (const auto & backend_b : b) { - if (backend_a == backend_b) { - bool already_added = false; - for (const auto & c : common) { - if (c == backend_a) { - already_added = true; - break; - } - } - if (!already_added) { - common.push_back(backend_a); - } - } + for (const auto & entry : a) { + if (std::find(b.begin(), b.end(), entry) != b.end() && + std::find(common.begin(), common.end(), entry) == common.end()) + { + common.push_back(entry); } } return common; @@ -246,12 +225,9 @@ std::vector BufferBackendRegistry::get_common_backends( void BufferBackendRegistry::clear_global_state() { - // CRITICAL: Clear backends_ map to release shared_ptr to plugin instances + // Clear backends_ map to release shared_ptr to plugin instances // before ClassLoader is destroyed. backends_.clear(); - - // Note: Global serialization maps in rosidl_typesupport_fastrtps_cpp are cleared - // by rmw_shutdown() which is called before this destructor runs. } } // namespace rosidl_buffer_backend_registry From 91e31b9633b7176c03d41996e3ce148931decd5e Mon Sep 17 00:00:00 2001 From: CY Chen Date: Sun, 22 Mar 2026 17:52:44 +0000 Subject: [PATCH 4/9] Rename backend aux info to backend metadata Signed-off-by: CY Chen --- .../buffer_backend_registry.hpp | 8 ++++---- .../src/buffer_backend_registry.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp index ce7022bdb..910a07439 100644 --- a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp @@ -77,10 +77,10 @@ class BufferBackendRegistry ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC std::vector get_backend_types(); - /// Collect aux info from all registered backends. - /// @return Map of backend name to aux info string. + /// Collect backend metadata from all registered backends. + /// @return Map of backend name to backend metadata string. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - std::unordered_map get_all_aux_info(); + std::unordered_map get_all_backend_metadata(); /// Notify all backends that a local endpoint is being created. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC @@ -90,7 +90,7 @@ class BufferBackendRegistry /// @param endpoint_info Information about the discovered endpoint. /// @param existing_endpoints List of existing endpoints for grouping decisions. /// @param backend_endpoint_groups In/out parameter for backend-specific grouping information. - /// @param endpoint_supported_backends Backend aux info from the discovered endpoint. + /// @param endpoint_supported_backends Backend metadata from the discovered endpoint. /// @return Map of backend name to compatibility boolean. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC std::unordered_map notify_endpoint_discovered( diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp index fd79ed769..1102197ae 100644 --- a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp +++ b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp @@ -151,20 +151,20 @@ std::vector BufferBackendRegistry::get_backend_types() return {types_set.begin(), types_set.end()}; } -std::unordered_map BufferBackendRegistry::get_all_aux_info() +std::unordered_map BufferBackendRegistry::get_all_backend_metadata() { // Ensure plugins are loaded if (!plugins_loaded_) { load_plugins(); } - std::unordered_map aux_info; + std::unordered_map backend_metadata; for (const auto & [name, backend] : backends_) { if (backend) { - aux_info[name] = backend->get_backend_aux_info(); + backend_metadata[name] = backend->get_backend_metadata(); } } - return aux_info; + return backend_metadata; } void BufferBackendRegistry::notify_endpoint_created( From 63a92433953048566250c5b957ad87be09841269 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Sun, 29 Mar 2026 03:53:57 +0000 Subject: [PATCH 5/9] Move buffer backend util functions to backend_utils.hpp Signed-off-by: CY Chen --- .../backend_utils.hpp | 127 ++++++++++ .../buffer_backend_registry.hpp | 83 +------ .../src/buffer_backend_registry.cpp | 227 ++++-------------- .../test/dummy_buffer_backend.hpp | 10 + .../test/test_buffer_backend_registry.cpp | 35 +-- 5 files changed, 210 insertions(+), 272 deletions(-) create mode 100644 rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/backend_utils.hpp diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/backend_utils.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/backend_utils.hpp new file mode 100644 index 000000000..cc328a9ec --- /dev/null +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/backend_utils.hpp @@ -0,0 +1,127 @@ +// Copyright 2026 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__BACKEND_UTILS_HPP_ +#define ROSIDL_BUFFER_BACKEND_REGISTRY__BACKEND_UTILS_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rosidl_buffer_backend/buffer_backend.hpp" +#include "rmw/topic_endpoint_info.h" + +namespace rosidl_buffer_backend_registry +{ + +/// Collect metadata strings from every loaded backend instance. +/// @param[in] backend_instances Map of backend name to backend instance. +/// @return Map of backend name to its metadata string (null backends are skipped). +inline std::unordered_map get_all_backend_metadata( + const std::unordered_map> & backend_instances) +{ + std::unordered_map backend_metadata; + for (const auto & [backend_name, backend] : backend_instances) { + if (backend) { + backend_metadata[backend_name] = backend->get_backend_metadata(); + } + } + return backend_metadata; +} + +/// Notify every loaded backend that a local endpoint has been created. +/// @param[in] backend_instances Map of backend name to backend instance. +/// @param[in] endpoint_info Endpoint that was just created. +inline void notify_endpoint_created( + const std::unordered_map> & backend_instances, + const rmw_topic_endpoint_info_t & endpoint_info) +{ + for (const auto & [_, backend] : backend_instances) { + if (backend) { + backend->on_creating_endpoint(endpoint_info); + } + } +} + +/// Notify every loaded backend that a remote endpoint has been discovered and +/// collect compatibility / grouping results. +/// @param[in] backend_instances Map of backend name to backend instance. +/// @param[in] endpoint_info Information about the discovered endpoint. +/// @param[in] existing_endpoints Endpoints already known on this topic. +/// @param[out] backend_endpoint_groups Updated per-backend endpoint GID-hash +/// groupings (cleared to empty for null backends). +/// @param[in] endpoint_supported_backends Backend-type-to-metadata map +/// advertised by the discovered endpoint. +/// @return Map of backend name to compatibility flag (false for null backends). +inline std::unordered_map notify_endpoint_discovered( + const std::unordered_map> & backend_instances, + const rmw_topic_endpoint_info_t & endpoint_info, + const std::vector & existing_endpoints, + std::unordered_map>> & backend_endpoint_groups, + const std::unordered_map & endpoint_supported_backends) +{ + std::unordered_map backend_compatibility; + for (const auto & [backend_name, backend] : backend_instances) { + if (!backend) { + backend_compatibility[backend_name] = false; + backend_endpoint_groups[backend_name] = {}; + continue; + } + auto result = backend->on_discovering_endpoint( + endpoint_info, existing_endpoints, endpoint_supported_backends); + backend_compatibility[backend_name] = result.first; + backend_endpoint_groups[backend_name] = std::move(result.second); + } + return backend_compatibility; +} + +/// Check whether two backend name lists share at least one common entry. +/// @return true if any backend name appears in both \p a and \p b. +inline bool backends_compatible( + const std::vector & a, + const std::vector & b) +{ + for (const auto & entry : a) { + if (std::find(b.begin(), b.end(), entry) != b.end()) { + return true; + } + } + return false; +} + +/// Return the de-duplicated intersection of two backend name lists, +/// preserving the order in which names appear in \p a. +inline std::vector get_common_backends( + const std::vector & a, + const std::vector & b) +{ + std::vector common; + for (const auto & entry : a) { + if (std::find(b.begin(), b.end(), entry) != b.end() && + std::find(common.begin(), common.end(), entry) == common.end()) + { + common.push_back(entry); + } + } + return common; +} + +} // namespace rosidl_buffer_backend_registry + +#endif // ROSIDL_BUFFER_BACKEND_REGISTRY__BACKEND_UTILS_HPP_ diff --git a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp index 910a07439..d8ac3ab50 100644 --- a/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp +++ b/rosidl_buffer_backend_registry/include/rosidl_buffer_backend_registry/buffer_backend_registry.hpp @@ -15,18 +15,13 @@ #ifndef ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ #define ROSIDL_BUFFER_BACKEND_REGISTRY__BUFFER_BACKEND_REGISTRY_HPP_ -#include #include -#include #include -#include #include #include "rosidl_buffer_backend/buffer_backend.hpp" #include "rosidl_buffer_backend_registry/visibility_control.h" -#include "rmw/topic_endpoint_info.h" - // Forward declare pluginlib ClassLoader to avoid header dependency namespace pluginlib { @@ -37,85 +32,26 @@ class ClassLoader; namespace rosidl_buffer_backend_registry { -/// Singleton registry for discovering and managing buffer backend plugins. +/// Registry for discovering and managing buffer backend plugins. /// Uses pluginlib for dynamic plugin discovery and loading. -/// -/// Thread-safety model: load_plugins() is guarded by std::call_once and -/// register_backend() is mutex-protected, so initialization is thread-safe. -/// After load_plugins() completes, the backends map is effectively immutable -/// and all read-only accessors (get_backend, get_backend_names, etc.) are -/// safe for concurrent use without additional synchronization. class BufferBackendRegistry { public: - /// Get the singleton instance. - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - static BufferBackendRegistry & get_instance(); - - /// Get a registered backend by name (e.g., "cpu", "cuda"). - /// @return The backend, or nullptr if not found. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - std::shared_ptr get_backend(const std::string & name); + BufferBackendRegistry(); - /// Manually register a backend (for built-in backends or testing). - /// Thread-safe (mutex-protected). ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - void register_backend(const std::string & name, std::shared_ptr backend); + ~BufferBackendRegistry(); - /// Load all available backend plugins via pluginlib. - /// Called automatically on first get_backend() if not already loaded. - /// Thread-safe (guarded by std::call_once). + /// Create a backend instance by plugin class name. + /// Backends loaded through pluginlib are instantiated per call. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - void load_plugins(); + std::shared_ptr create_backend_instance(const std::string & name); /// Get names of all registered backends. ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC std::vector get_backend_names() const; - /// Get backend type strings for all registered backends, always including "cpu". - /// Each backend's get_backend_type() is queried (e.g., "cuda", "rocm"). - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - std::vector get_backend_types(); - - /// Collect backend metadata from all registered backends. - /// @return Map of backend name to backend metadata string. - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - std::unordered_map get_all_backend_metadata(); - - /// Notify all backends that a local endpoint is being created. - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - void notify_endpoint_created(const rmw_topic_endpoint_info_t & endpoint_info); - - /// Notify all backends that a remote endpoint has been discovered. - /// @param endpoint_info Information about the discovered endpoint. - /// @param existing_endpoints List of existing endpoints for grouping decisions. - /// @param backend_endpoint_groups In/out parameter for backend-specific grouping information. - /// @param endpoint_supported_backends Backend metadata from the discovered endpoint. - /// @return Map of backend name to compatibility boolean. - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - std::unordered_map notify_endpoint_discovered( - const rmw_topic_endpoint_info_t & endpoint_info, - const std::vector & existing_endpoints, - std::unordered_map>> & backend_endpoint_groups, - const std::unordered_map & endpoint_supported_backends); - - /// Check if two backend type lists have at least one common entry. - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - static bool backends_compatible( - const std::vector & a, - const std::vector & b); - - /// Get the intersection of two backend type lists. - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - static std::vector get_common_backends( - const std::vector & a, - const std::vector & b); - - /// Clear all backends and release plugin instances. - /// Called automatically in destructor before ClassLoader is destroyed. - ROSIDL_BUFFER_BACKEND_REGISTRY_PUBLIC - void clear_global_state(); - // Non-copyable, non-movable BufferBackendRegistry(const BufferBackendRegistry &) = delete; BufferBackendRegistry & operator=(const BufferBackendRegistry &) = delete; @@ -123,12 +59,11 @@ class BufferBackendRegistry BufferBackendRegistry & operator=(BufferBackendRegistry &&) = delete; private: - BufferBackendRegistry(); - ~BufferBackendRegistry(); + /// Query pluginlib for declared backend classes and populate plugin_backend_classes_. + void load_plugins(); - std::map> backends_; + std::vector plugin_backend_classes_; std::unique_ptr> loader_; - bool plugins_loaded_; }; } // namespace rosidl_buffer_backend_registry diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp index 1102197ae..11608909c 100644 --- a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp +++ b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp @@ -15,219 +15,98 @@ #include "rosidl_buffer_backend_registry/buffer_backend_registry.hpp" #include -#include #include #include +#include "rcutils/logging_macros.h" namespace rosidl_buffer_backend_registry { BufferBackendRegistry::BufferBackendRegistry() -: plugins_loaded_(false) { try { loader_ = std::make_unique>( "rosidl_buffer_backend", "rosidl::BufferBackend"); } catch (const std::exception & e) { - // Silently ignore if pluginlib isn't available - will work without plugins + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Failed to create ClassLoader: %s", e.what()); loader_ = nullptr; } + load_plugins(); } -BufferBackendRegistry::~BufferBackendRegistry() -{ - // CRITICAL: Clear all global state before ClassLoader is destroyed - clear_global_state(); -} - -BufferBackendRegistry & BufferBackendRegistry::get_instance() -{ - static BufferBackendRegistry instance; - return instance; -} - -std::shared_ptr BufferBackendRegistry::get_backend(const std::string & name) -{ - // Ensure plugins are loaded on first access - if (!plugins_loaded_) { - load_plugins(); - } - - auto it = backends_.find(name); - if (it != backends_.end()) { - return it->second; - } - - return nullptr; -} - -void BufferBackendRegistry::register_backend( - const std::string & name, - std::shared_ptr backend) -{ - static std::mutex mutex; - std::lock_guard lock(mutex); - backends_[name] = backend; -} +BufferBackendRegistry::~BufferBackendRegistry() = default; void BufferBackendRegistry::load_plugins() { - static std::once_flag load_flag; - std::call_once( - load_flag, [this]() { - if (!loader_) { - plugins_loaded_ = true; - return; - } + if (!loader_) { + return; + } - try { - auto declared_classes = loader_->getDeclaredClasses(); - if (declared_classes.empty()) { - RCUTILS_LOG_INFO_NAMED( - "rosidl_buffer_backend_registry", - "No buffer backend plugins found"); - } else { - RCUTILS_LOG_INFO_NAMED( - "rosidl_buffer_backend_registry", - "Discovered %zu buffer backend plugin(s)", - declared_classes.size()); - for (const auto & class_name : declared_classes) { - try { - auto backend = loader_->createSharedInstance(class_name); - register_backend(class_name, backend); - RCUTILS_LOG_INFO_NAMED( - "rosidl_buffer_backend_registry", - "Loaded buffer backend plugin: %s", - class_name.c_str()); - } catch (const std::exception & e) { - RCUTILS_LOG_ERROR_NAMED( - "rosidl_buffer_backend_registry", - "Failed to load %s: %s", - class_name.c_str(), e.what()); - continue; - } - } - } - } catch (const std::exception & e) { - RCUTILS_LOG_ERROR_NAMED( + try { + auto declared_classes = loader_->getDeclaredClasses(); + if (declared_classes.empty()) { + RCUTILS_LOG_INFO_NAMED( + "rosidl_buffer_backend_registry", + "No buffer backend plugins found"); + } else { + RCUTILS_LOG_INFO_NAMED( + "rosidl_buffer_backend_registry", + "Discovered %zu buffer backend plugin(s)", + declared_classes.size()); + for (const auto & class_name : declared_classes) { + plugin_backend_classes_.push_back(class_name); + RCUTILS_LOG_INFO_NAMED( "rosidl_buffer_backend_registry", - "Buffer backend plugin discovery error: %s", - e.what()); + "Discovered buffer backend plugin class: %s", + class_name.c_str()); } - - plugins_loaded_ = true; - }); -} - -std::vector BufferBackendRegistry::get_backend_names() const -{ - std::vector names; - names.reserve(backends_.size()); - for (const auto & [name, _] : backends_) { - names.push_back(name); - } - return names; -} - -std::vector BufferBackendRegistry::get_backend_types() -{ - // Ensure plugins are loaded - if (!plugins_loaded_) { - load_plugins(); - } - - std::set types_set; - // CPU is always implicitly available (Buffer defaults to CpuBufferImpl) - types_set.insert("cpu"); - - for (const auto & [_, backend] : backends_) { - if (backend) { - types_set.insert(backend->get_backend_type()); } + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Buffer backend plugin discovery error: %s", + e.what()); } - - return {types_set.begin(), types_set.end()}; } -std::unordered_map BufferBackendRegistry::get_all_backend_metadata() +std::shared_ptr BufferBackendRegistry::create_backend_instance( + const std::string & name) { - // Ensure plugins are loaded - if (!plugins_loaded_) { - load_plugins(); + if (!loader_) { + return nullptr; } - std::unordered_map backend_metadata; - for (const auto & [name, backend] : backends_) { - if (backend) { - backend_metadata[name] = backend->get_backend_metadata(); - } + auto plugin_it = std::find( + plugin_backend_classes_.begin(), plugin_backend_classes_.end(), name); + if (plugin_it == plugin_backend_classes_.end()) { + return nullptr; } - return backend_metadata; -} - -void BufferBackendRegistry::notify_endpoint_created( - const rmw_topic_endpoint_info_t & endpoint_info) -{ - for (const auto & [_, backend] : backends_) { - if (backend) { - backend->on_creating_endpoint(endpoint_info); - } - } -} -std::unordered_map BufferBackendRegistry::notify_endpoint_discovered( - const rmw_topic_endpoint_info_t & endpoint_info, - const std::vector & existing_endpoints, - std::unordered_map>> & backend_endpoint_groups, - const std::unordered_map & endpoint_supported_backends) -{ - std::unordered_map backend_compatibility; - for (const auto & [backend_name, backend] : backends_) { - if (!backend) { - backend_compatibility[backend_name] = false; - backend_endpoint_groups[backend_name] = {}; - continue; - } - auto result = backend->on_discovering_endpoint( - endpoint_info, existing_endpoints, endpoint_supported_backends); - backend_compatibility[backend_name] = result.first; - backend_endpoint_groups[backend_name] = std::move(result.second); + try { + return loader_->createSharedInstance(name); + } catch (const std::exception & e) { + RCUTILS_LOG_ERROR_NAMED( + "rosidl_buffer_backend_registry", + "Failed to instantiate backend '%s': %s", + name.c_str(), e.what()); + return nullptr; } - return backend_compatibility; -} - -bool BufferBackendRegistry::backends_compatible( - const std::vector & a, - const std::vector & b) -{ - return std::any_of( - a.begin(), a.end(), [&b](const std::string & entry) { - return std::find(b.begin(), b.end(), entry) != b.end(); - }); } -std::vector BufferBackendRegistry::get_common_backends( - const std::vector & a, - const std::vector & b) +std::vector BufferBackendRegistry::get_backend_names() const { - std::vector common; - for (const auto & entry : a) { - if (std::find(b.begin(), b.end(), entry) != b.end() && - std::find(common.begin(), common.end(), entry) == common.end()) - { - common.push_back(entry); + std::vector names; + names.reserve(plugin_backend_classes_.size()); + for (const auto & class_name : plugin_backend_classes_) { + if (std::find(names.begin(), names.end(), class_name) == names.end()) { + names.push_back(class_name); } } - return common; -} - -void BufferBackendRegistry::clear_global_state() -{ - // Clear backends_ map to release shared_ptr to plugin instances - // before ClassLoader is destroyed. - backends_.clear(); + return names; } } // namespace rosidl_buffer_backend_registry diff --git a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp index 97d0b1a86..cd10d0aa6 100644 --- a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp +++ b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp @@ -93,6 +93,16 @@ class DummyBufferBackend : public rosidl::BufferBackend return "dummy"; } + const rosidl_message_type_support_t * get_descriptor_type_support() const override + { + return nullptr; + } + + std::shared_ptr create_empty_descriptor() const override + { + return nullptr; + } + std::shared_ptr create_descriptor_with_endpoint( const std::shared_ptr & impl, const rmw_topic_endpoint_info_t & endpoint_info) const override diff --git a/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp index 14353dd2e..a05b107ba 100644 --- a/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp +++ b/rosidl_buffer_backend_registry/test/test_buffer_backend_registry.cpp @@ -21,35 +21,22 @@ #include "dummy_buffer_backend.hpp" using rosidl_buffer_backend_registry::BufferBackendRegistry; -using rosidl_buffer_backend_registry::test::DummyBufferBackend; using rosidl_buffer_backend_registry::test::DummyBufferImpl; -// Test getting singleton instance -TEST(TestBufferBackendRegistry, get_instance) { - auto & registry1 = BufferBackendRegistry::get_instance(); - auto & registry2 = BufferBackendRegistry::get_instance(); +// Test registries are ordinary context-owned objects. +TEST(TestBufferBackendRegistry, independent_instances) { + auto registry1 = std::make_unique(); + auto registry2 = std::make_unique(); - // Should be the same instance - EXPECT_EQ(®istry1, ®istry2); + ASSERT_NE(nullptr, registry1); + ASSERT_NE(nullptr, registry2); + EXPECT_NE(registry1.get(), registry2.get()); } -// Test registering and getting a backend -TEST(TestBufferBackendRegistry, register_and_get_backend) { - auto & registry = BufferBackendRegistry::get_instance(); - - auto dummy_backend = std::make_shared(); - registry.register_backend("dummy_test", dummy_backend); - - auto retrieved = registry.get_backend("dummy_test"); - ASSERT_NE(nullptr, retrieved); - EXPECT_EQ(dummy_backend.get(), retrieved.get()); -} - -// Test getting non-existent backend -TEST(TestBufferBackendRegistry, get_nonexistent_backend) { - auto & registry = BufferBackendRegistry::get_instance(); - - auto backend = registry.get_backend("nonexistent_backend_12345"); +// Test creating a non-existent backend instance +TEST(TestBufferBackendRegistry, create_nonexistent_backend_instance) { + BufferBackendRegistry registry; + auto backend = registry.create_backend_instance("nonexistent_backend_12345"); EXPECT_EQ(nullptr, backend); } From baedec4dcfdc2b055c6dd848f7274ce1ad6f7b59 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Sun, 29 Mar 2026 18:54:00 +0000 Subject: [PATCH 6/9] Update test backend to use non-owning pointer in descriptor ops Signed-off-by: CY Chen --- .../test/dummy_buffer_backend.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp index cd10d0aa6..428097e63 100644 --- a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp +++ b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp @@ -104,20 +104,18 @@ class DummyBufferBackend : public rosidl::BufferBackend } std::shared_ptr create_descriptor_with_endpoint( - const std::shared_ptr & impl, + const void * impl, const rmw_topic_endpoint_info_t & endpoint_info) const override { - // Return nullptr - not used in these tests (void)impl; (void)endpoint_info; return nullptr; } std::shared_ptr from_descriptor_with_endpoint( - const std::shared_ptr & descriptor, + const void * descriptor, const rmw_topic_endpoint_info_t & endpoint_info) const override { - // Return nullptr - not used in these tests (void)descriptor; (void)endpoint_info; return nullptr; From b3d878733d9b4d745224316b2d6ae027c9a5aaa9 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Sun, 29 Mar 2026 20:17:49 +0000 Subject: [PATCH 7/9] Update test backend from_descriptor_with_endpoint with type-erased unique pointer Signed-off-by: CY Chen --- rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp index 428097e63..90081190a 100644 --- a/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp +++ b/rosidl_buffer_backend_registry/test/dummy_buffer_backend.hpp @@ -112,13 +112,13 @@ class DummyBufferBackend : public rosidl::BufferBackend return nullptr; } - std::shared_ptr from_descriptor_with_endpoint( + std::unique_ptr from_descriptor_with_endpoint( const void * descriptor, const rmw_topic_endpoint_info_t & endpoint_info) const override { (void)descriptor; (void)endpoint_info; - return nullptr; + return {nullptr, [](void *) {}}; } }; From b13d76f1047d671c95339ca684154954a1313c00 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Wed, 1 Apr 2026 00:20:54 +0000 Subject: [PATCH 8/9] Adjust log level Signed-off-by: CY Chen --- .../src/buffer_backend_registry.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp index 11608909c..7f9037616 100644 --- a/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp +++ b/rosidl_buffer_backend_registry/src/buffer_backend_registry.cpp @@ -49,17 +49,17 @@ void BufferBackendRegistry::load_plugins() try { auto declared_classes = loader_->getDeclaredClasses(); if (declared_classes.empty()) { - RCUTILS_LOG_INFO_NAMED( + RCUTILS_LOG_DEBUG_NAMED( "rosidl_buffer_backend_registry", "No buffer backend plugins found"); } else { - RCUTILS_LOG_INFO_NAMED( + RCUTILS_LOG_DEBUG_NAMED( "rosidl_buffer_backend_registry", "Discovered %zu buffer backend plugin(s)", declared_classes.size()); for (const auto & class_name : declared_classes) { plugin_backend_classes_.push_back(class_name); - RCUTILS_LOG_INFO_NAMED( + RCUTILS_LOG_DEBUG_NAMED( "rosidl_buffer_backend_registry", "Discovered buffer backend plugin class: %s", class_name.c_str()); From c41bf17b8e2cf6e0e9e13333f7fa04a1c4041218 Mon Sep 17 00:00:00 2001 From: CY Chen Date: Wed, 1 Apr 2026 16:17:41 +0000 Subject: [PATCH 9/9] Add rosidl_buffer dependency for rosidl_buffer_backend_registry test Signed-off-by: CY Chen --- rosidl_buffer_backend_registry/CMakeLists.txt | 2 ++ rosidl_buffer_backend_registry/package.xml | 1 + 2 files changed, 3 insertions(+) diff --git a/rosidl_buffer_backend_registry/CMakeLists.txt b/rosidl_buffer_backend_registry/CMakeLists.txt index 2c6eb93e6..342c39d85 100644 --- a/rosidl_buffer_backend_registry/CMakeLists.txt +++ b/rosidl_buffer_backend_registry/CMakeLists.txt @@ -12,6 +12,7 @@ endif() # Find dependencies find_package(ament_cmake REQUIRED) +find_package(rosidl_buffer REQUIRED) find_package(rosidl_buffer_backend REQUIRED) find_package(rmw REQUIRED) find_package(rosidl_runtime_cpp REQUIRED) @@ -74,6 +75,7 @@ if(BUILD_TESTING) ) target_link_libraries(test_buffer_backend_registry ${PROJECT_NAME} + rosidl_buffer::rosidl_buffer rosidl_buffer_backend::rosidl_buffer_backend rmw::rmw rosidl_runtime_cpp::rosidl_runtime_cpp diff --git a/rosidl_buffer_backend_registry/package.xml b/rosidl_buffer_backend_registry/package.xml index 1882d2dee..27d392275 100644 --- a/rosidl_buffer_backend_registry/package.xml +++ b/rosidl_buffer_backend_registry/package.xml @@ -20,6 +20,7 @@ ament_lint_auto ament_lint_common + rosidl_buffer ament_cmake