Check period duration in create_wall_timer (#1178)
* Check period duration in create_wall_timer Signed-off-by: Stephen Brawner <brawner@gmail.com> * Adding comments Signed-off-by: Stephen Brawner <brawner@gmail.com>
This commit is contained in:
parent
823e1dd404
commit
00b4020194
2 changed files with 89 additions and 6 deletions
|
@ -76,14 +76,14 @@ create_timer(
|
||||||
* \tparam DurationRepT
|
* \tparam DurationRepT
|
||||||
* \tparam DurationT
|
* \tparam DurationT
|
||||||
* \tparam CallbackT
|
* \tparam CallbackT
|
||||||
* \param period period to exectute callback
|
* \param period period to execute callback. This duration must be 0 <= period < nanoseconds::max()
|
||||||
* \param callback callback to execute via the timer period
|
* \param callback callback to execute via the timer period
|
||||||
* \param group
|
* \param group
|
||||||
* \param node_base
|
* \param node_base
|
||||||
* \param node_timers
|
* \param node_timers
|
||||||
* \return
|
* \return
|
||||||
* \throws std::invalid argument if either node_base or node_timers
|
* \throws std::invalid argument if either node_base or node_timers
|
||||||
* are null
|
* are null, or period is negative or too large
|
||||||
*/
|
*/
|
||||||
template<typename DurationRepT, typename DurationT, typename CallbackT>
|
template<typename DurationRepT, typename DurationT, typename CallbackT>
|
||||||
typename rclcpp::WallTimer<CallbackT>::SharedPtr
|
typename rclcpp::WallTimer<CallbackT>::SharedPtr
|
||||||
|
@ -102,10 +102,38 @@ create_wall_timer(
|
||||||
throw std::invalid_argument{"input node_timers cannot be null"};
|
throw std::invalid_argument{"input node_timers cannot be null"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (period < std::chrono::duration<DurationRepT, DurationT>::zero()) {
|
||||||
|
throw std::invalid_argument{"timer period cannot be negative"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Casting to a double representation might lose precision and allow the check below to succeed
|
||||||
|
// but the actual cast to nanoseconds fail. Using 1 DurationT worth of nanoseconds less than max.
|
||||||
|
constexpr auto maximum_safe_cast_ns =
|
||||||
|
std::chrono::nanoseconds::max() - std::chrono::duration<DurationRepT, DurationT>(1);
|
||||||
|
|
||||||
|
// If period is greater than nanoseconds::max(), the duration_cast to nanoseconds will overflow
|
||||||
|
// a signed integer, which is undefined behavior. Checking whether any std::chrono::duration is
|
||||||
|
// greater than nanoseconds::max() is a difficult general problem. This is a more conservative
|
||||||
|
// version of Howard Hinnant's (the <chrono> guy>) response here:
|
||||||
|
// https://stackoverflow.com/a/44637334/2089061
|
||||||
|
// However, this doesn't solve the issue for all possible duration types of period.
|
||||||
|
// Follow-up issue: https://github.com/ros2/rclcpp/issues/1177
|
||||||
|
constexpr auto ns_max_as_double =
|
||||||
|
std::chrono::duration_cast<std::chrono::duration<double, std::chrono::nanoseconds::period>>(
|
||||||
|
maximum_safe_cast_ns);
|
||||||
|
if (period > ns_max_as_double) {
|
||||||
|
throw std::invalid_argument{
|
||||||
|
"timer period must be less than std::chrono::nanoseconds::max()"};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto period_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(period);
|
||||||
|
if (period_ns < std::chrono::nanoseconds::zero()) {
|
||||||
|
throw std::runtime_error{
|
||||||
|
"Casting timer period to nanoseconds resulted in integer overflow."};
|
||||||
|
}
|
||||||
|
|
||||||
auto timer = rclcpp::WallTimer<CallbackT>::make_shared(
|
auto timer = rclcpp::WallTimer<CallbackT>::make_shared(
|
||||||
std::chrono::duration_cast<std::chrono::nanoseconds>(period),
|
period_ns, std::move(callback), node_base->get_context());
|
||||||
std::move(callback),
|
|
||||||
node_base->get_context());
|
|
||||||
node_timers->add_timer(timer, group);
|
node_timers->add_timer(timer, group);
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "node_interfaces/node_wrapper.hpp"
|
||||||
#include "rclcpp/create_timer.hpp"
|
#include "rclcpp/create_timer.hpp"
|
||||||
#include "rclcpp/executors.hpp"
|
#include "rclcpp/executors.hpp"
|
||||||
#include "rclcpp/node.hpp"
|
#include "rclcpp/node.hpp"
|
||||||
#include "node_interfaces/node_wrapper.hpp"
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
@ -61,3 +61,58 @@ TEST(TestCreateTimer, call_with_node_wrapper_compiles)
|
||||||
[]() {});
|
[]() {});
|
||||||
rclcpp::shutdown();
|
rclcpp::shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(TestCreateTimer, call_wall_timer_with_bad_arguments)
|
||||||
|
{
|
||||||
|
rclcpp::init(0, nullptr);
|
||||||
|
NodeWrapper node("test_create_wall_timers_with_bad_arguments");
|
||||||
|
auto callback = []() {};
|
||||||
|
rclcpp::CallbackGroup::SharedPtr group = nullptr;
|
||||||
|
auto node_interface =
|
||||||
|
rclcpp::node_interfaces::get_node_base_interface(node).get();
|
||||||
|
auto timers_interface =
|
||||||
|
rclcpp::node_interfaces::get_node_timers_interface(node).get();
|
||||||
|
|
||||||
|
// Negative period
|
||||||
|
EXPECT_THROW(
|
||||||
|
rclcpp::create_wall_timer(-1ms, callback, group, node_interface, timers_interface),
|
||||||
|
std::invalid_argument);
|
||||||
|
|
||||||
|
// Very negative period
|
||||||
|
constexpr auto nanoseconds_min = std::chrono::nanoseconds::min();
|
||||||
|
EXPECT_THROW(
|
||||||
|
rclcpp::create_wall_timer(
|
||||||
|
nanoseconds_min, callback, group, node_interface, timers_interface),
|
||||||
|
std::invalid_argument);
|
||||||
|
|
||||||
|
// Period must be less than nanoseconds::max()
|
||||||
|
constexpr auto nanoseconds_max = std::chrono::nanoseconds::min();
|
||||||
|
EXPECT_THROW(
|
||||||
|
rclcpp::create_wall_timer(
|
||||||
|
nanoseconds_max, callback, group, node_interface, timers_interface),
|
||||||
|
std::invalid_argument);
|
||||||
|
|
||||||
|
EXPECT_NO_THROW(
|
||||||
|
rclcpp::create_wall_timer(
|
||||||
|
nanoseconds_max - 1us, callback, group, node_interface, timers_interface));
|
||||||
|
|
||||||
|
EXPECT_NO_THROW(
|
||||||
|
rclcpp::create_wall_timer(0ms, callback, group, node_interface, timers_interface));
|
||||||
|
|
||||||
|
// Period must be less than nanoseconds::max()
|
||||||
|
constexpr auto hours_max = std::chrono::hours::max();
|
||||||
|
EXPECT_THROW(
|
||||||
|
rclcpp::create_wall_timer(hours_max, callback, group, node_interface, timers_interface),
|
||||||
|
std::invalid_argument);
|
||||||
|
|
||||||
|
// node_interface is null
|
||||||
|
EXPECT_THROW(
|
||||||
|
rclcpp::create_wall_timer(1ms, callback, group, nullptr, timers_interface),
|
||||||
|
std::invalid_argument);
|
||||||
|
|
||||||
|
// timers_interface is null
|
||||||
|
EXPECT_THROW(
|
||||||
|
rclcpp::create_wall_timer(1ms, callback, group, node_interface, nullptr),
|
||||||
|
std::invalid_argument);
|
||||||
|
rclcpp::shutdown();
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue