diff --git a/rclpy/CMakeLists.txt b/rclpy/CMakeLists.txt index 65df66233..fbba007f9 100644 --- a/rclpy/CMakeLists.txt +++ b/rclpy/CMakeLists.txt @@ -239,6 +239,11 @@ if(BUILD_TESTING) target_link_libraries(test_c_handle rclpy_common) + ament_add_gtest(test_python_allocator + test/test_python_allocator.cpp) + target_include_directories(test_python_allocator PRIVATE src/rclpy) + target_link_libraries(test_python_allocator pybind11::embed) + if(NOT _typesupport_impls STREQUAL "") # Run each test in its own pytest invocation to isolate any global state in rclpy set(_rclpy_pytest_tests diff --git a/rclpy/src/rclpy/client.cpp b/rclpy/src/rclpy/client.cpp index a171d568e..ad3edd67d 100644 --- a/rclpy/src/rclpy/client.cpp +++ b/rclpy/src/rclpy/client.cpp @@ -23,6 +23,7 @@ #include "rclpy_common/common.h" #include "client.hpp" +#include "python_allocator.hpp" #include "rclpy_common/exceptions.hpp" #include "utils.hpp" @@ -58,9 +59,10 @@ Client::Client( client_ops.qos = *qos_profile; } + // Create a client rcl_client_ = std::shared_ptr( - new rcl_client_t, + PythonAllocator().allocate(1), [this](rcl_client_t * client) { auto node = node_handle_->cast_or_warn("rcl_node_t"); @@ -74,7 +76,7 @@ Client::Client( rcl_get_error_string().str); rcl_reset_error(); } - delete client; + PythonAllocator().deallocate(client, 1); }); *rcl_client_ = rcl_get_zero_initialized_client(); diff --git a/rclpy/src/rclpy/python_allocator.hpp b/rclpy/src/rclpy/python_allocator.hpp new file mode 100644 index 000000000..1164702a8 --- /dev/null +++ b/rclpy/src/rclpy/python_allocator.hpp @@ -0,0 +1,80 @@ +// Copyright 2021 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 RCLPY__PYTHON_ALLOCATOR_HPP_ +#define RCLPY__PYTHON_ALLOCATOR_HPP_ + +#include + +namespace rclpy +{ +//----------- Declaration ---------- + +/// Allocate memory using Python's allocator +/** + * \warning The GIL must be held while the allocator is used. + */ +template +struct PythonAllocator +{ + using value_type = T; + + PythonAllocator() noexcept = default; + + template + PythonAllocator(const PythonAllocator & /* other */) noexcept {} + + T * allocate(std::size_t n); + + void deallocate(T * p, std::size_t n); +}; + +template +constexpr bool operator==(const PythonAllocator &, const PythonAllocator &) noexcept; + +template +constexpr bool operator!=(const PythonAllocator &, const PythonAllocator &) noexcept; + +//----------- Implementation ---------- + +template +T * PythonAllocator::allocate(std::size_t n) +{ + T * ptr = static_cast(PyMem_Malloc(n * sizeof(T))); + if (nullptr == ptr) { + throw std::bad_alloc(); + } + return ptr; +} + +template +void PythonAllocator::deallocate(T * p, std::size_t /* n */) +{ + PyMem_Free(p); +} + +template +constexpr bool operator==(const PythonAllocator &, const PythonAllocator &) noexcept +{ + return true; +} + +template +constexpr bool operator!=(const PythonAllocator &, const PythonAllocator &) noexcept +{ + return false; +} +} // namespace rclpy + +#endif // RCLPY__PYTHON_ALLOCATOR_HPP_ diff --git a/rclpy/test/test_python_allocator.cpp b/rclpy/test/test_python_allocator.cpp new file mode 100644 index 000000000..f7c0e7d48 --- /dev/null +++ b/rclpy/test/test_python_allocator.cpp @@ -0,0 +1,71 @@ +// Copyright 2021 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 "python_allocator.hpp" + +namespace py = pybind11; + +TEST(test_allocator, vector) { + py::scoped_interpreter guard{}; // Start a Python interpreter + + std::vector> container(42); + + EXPECT_EQ(42u, container.capacity()); + ASSERT_EQ(42u, container.size()); + + for (size_t i = 0; i < 42u; ++i) { + container[i] = i; + } +} + +TEST(test_allocator, equality) { + py::scoped_interpreter guard{}; // Start a Python interpreter + + rclpy::PythonAllocator int_alloc; + rclpy::PythonAllocator float_alloc; + + EXPECT_TRUE(int_alloc == float_alloc); + EXPECT_FALSE(int_alloc != float_alloc); +} + +TEST(test_allocator, make_1) { + py::scoped_interpreter guard{}; // Start a Python interpreter + + rclpy::PythonAllocator int_alloc; + + int * i = int_alloc.allocate(1); + + ASSERT_NE(nullptr, i); + + int_alloc.deallocate(i, 1); +} + +TEST(test_allocator, copy_construct_make_1) { + py::scoped_interpreter guard{}; // Start a Python interpreter + + rclpy::PythonAllocator float_alloc; + rclpy::PythonAllocator int_alloc(float_alloc); + + int * i = int_alloc.allocate(1); + + ASSERT_NE(nullptr, i); + + int_alloc.deallocate(i, 1); +}