Switch the NodeParameters lock to recursive. (#781)

* Switch the NodeParameters lock to recursive.

This is so that the on_set_parameter_callback can successfully
call other parameter methods (like get_parameter) without
deadlocking.

Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>

* Make sure that modifications can't happen within a callback.

Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>

* Review fixes.

Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>

* Set parameter_modification_enabled_ in calls that make modifications.

This will prevent any modification from within modification,
which is a good thing.

Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>

* Fix windows errors.

Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>

* Switch to an RAII-style recursion guard.

Also update the documentation.

Signed-off-by: Chris Lalancette <clalancette@openrobotics.org>
This commit is contained in:
Chris Lalancette 2019-07-11 14:56:04 -04:00 committed by GitHub
parent 207bcc5de3
commit f8d609496e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 250 additions and 12 deletions

View file

@ -219,6 +219,13 @@ class ParameterImmutableException : public std::runtime_error
using std::runtime_error::runtime_error; using std::runtime_error::runtime_error;
}; };
/// Thrown if parameter is modified while in a set callback.
class ParameterModifiedInCallbackException : public std::runtime_error
{
// Inherit constructors from runtime_error.
using std::runtime_error::runtime_error;
};
} // namespace exceptions } // namespace exceptions
} // namespace rclcpp } // namespace rclcpp

View file

@ -928,6 +928,14 @@ public:
* *
* Some constraints like read_only are enforced before the callback is called. * Some constraints like read_only are enforced before the callback is called.
* *
* The callback may introspect other already set parameters (by calling any
* of the {get,list,describe}_parameter() methods), but may *not* modify
* other parameters (by calling any of the {set,declare}_parameter() methods)
* or modify the registered callback itself (by calling the
* set_on_parameters_set_callback() method). If a callback tries to do any
* of the latter things,
* rclcpp::exceptions::ParameterModifiedInCallbackException will be thrown.
*
* There may only be one callback set at a time, so the previously set * There may only be one callback set at a time, so the previously set
* callback is returned when this method is used, or nullptr will be returned * callback is returned when this method is used, or nullptr will be returned
* if no callback was previously set. * if no callback was previously set.

View file

@ -50,6 +50,30 @@ struct ParameterInfo
rcl_interfaces::msg::ParameterDescriptor descriptor; rcl_interfaces::msg::ParameterDescriptor descriptor;
}; };
// Internal RAII-style guard for mutation recursion
class ParameterMutationRecursionGuard
{
public:
explicit ParameterMutationRecursionGuard(bool & allow_mod)
: allow_modification_(allow_mod)
{
if (!allow_modification_) {
throw rclcpp::exceptions::ParameterModifiedInCallbackException(
"cannot set or declare a parameter, or change the callback from within set callback");
}
allow_modification_ = false;
}
~ParameterMutationRecursionGuard()
{
allow_modification_ = true;
}
private:
bool & allow_modification_;
};
/// Implementation of the NodeParameters part of the Node API. /// Implementation of the NodeParameters part of the Node API.
class NodeParameters : public NodeParametersInterface class NodeParameters : public NodeParametersInterface
{ {
@ -149,7 +173,12 @@ public:
private: private:
RCLCPP_DISABLE_COPY(NodeParameters) RCLCPP_DISABLE_COPY(NodeParameters)
mutable std::mutex mutex_; mutable std::recursive_mutex mutex_;
// There are times when we don't want to allow modifications to parameters
// (particularly when a set_parameter callback tries to call set_parameter,
// declare_parameter, etc). In those cases, this will be set to false.
bool parameter_modification_enabled_{true};
OnParametersSetCallbackType on_parameters_set_callback_ = nullptr; OnParametersSetCallbackType on_parameters_set_callback_ = nullptr;

View file

@ -374,7 +374,9 @@ NodeParameters::declare_parameter(
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor, const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor,
bool ignore_override) bool ignore_override)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
ParameterMutationRecursionGuard guard(parameter_modification_enabled_);
// TODO(sloretz) parameter name validation // TODO(sloretz) parameter name validation
if (name.empty()) { if (name.empty()) {
@ -415,7 +417,9 @@ NodeParameters::declare_parameter(
void void
NodeParameters::undeclare_parameter(const std::string & name) NodeParameters::undeclare_parameter(const std::string & name)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
ParameterMutationRecursionGuard guard(parameter_modification_enabled_);
auto parameter_info = parameters_.find(name); auto parameter_info = parameters_.find(name);
if (parameter_info == parameters_.end()) { if (parameter_info == parameters_.end()) {
@ -434,7 +438,7 @@ NodeParameters::undeclare_parameter(const std::string & name)
bool bool
NodeParameters::has_parameter(const std::string & name) const NodeParameters::has_parameter(const std::string & name) const
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
return __lockless_has_parameter(parameters_, name); return __lockless_has_parameter(parameters_, name);
} }
@ -468,7 +472,9 @@ __find_parameter_by_name(
rcl_interfaces::msg::SetParametersResult rcl_interfaces::msg::SetParametersResult
NodeParameters::set_parameters_atomically(const std::vector<rclcpp::Parameter> & parameters) NodeParameters::set_parameters_atomically(const std::vector<rclcpp::Parameter> & parameters)
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
ParameterMutationRecursionGuard guard(parameter_modification_enabled_);
rcl_interfaces::msg::SetParametersResult result; rcl_interfaces::msg::SetParametersResult result;
@ -575,7 +581,7 @@ NodeParameters::set_parameters_atomically(const std::vector<rclcpp::Parameter> &
} }
} }
// Set the all of the parameters including the ones declared implicitly above. // Set all of the parameters including the ones declared implicitly above.
result = __set_parameters_atomically_common( result = __set_parameters_atomically_common(
// either the original parameters given by the user, or ones updated with initial values // either the original parameters given by the user, or ones updated with initial values
*parameters_to_be_set, *parameters_to_be_set,
@ -644,7 +650,7 @@ NodeParameters::set_parameters_atomically(const std::vector<rclcpp::Parameter> &
std::vector<rclcpp::Parameter> std::vector<rclcpp::Parameter>
NodeParameters::get_parameters(const std::vector<std::string> & names) const NodeParameters::get_parameters(const std::vector<std::string> & names) const
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
std::vector<rclcpp::Parameter> results; std::vector<rclcpp::Parameter> results;
results.reserve(names.size()); results.reserve(names.size());
@ -683,7 +689,7 @@ NodeParameters::get_parameter(
const std::string & name, const std::string & name,
rclcpp::Parameter & parameter) const rclcpp::Parameter & parameter) const
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
auto param_iter = parameters_.find(name); auto param_iter = parameters_.find(name);
if ( if (
@ -702,7 +708,7 @@ NodeParameters::get_parameters_by_prefix(
const std::string & prefix, const std::string & prefix,
std::map<std::string, rclcpp::Parameter> & parameters) const std::map<std::string, rclcpp::Parameter> & parameters) const
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
std::string prefix_with_dot = prefix.empty() ? prefix : prefix + "."; std::string prefix_with_dot = prefix.empty() ? prefix : prefix + ".";
bool ret = false; bool ret = false;
@ -721,7 +727,7 @@ NodeParameters::get_parameters_by_prefix(
std::vector<rcl_interfaces::msg::ParameterDescriptor> std::vector<rcl_interfaces::msg::ParameterDescriptor>
NodeParameters::describe_parameters(const std::vector<std::string> & names) const NodeParameters::describe_parameters(const std::vector<std::string> & names) const
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
std::vector<rcl_interfaces::msg::ParameterDescriptor> results; std::vector<rcl_interfaces::msg::ParameterDescriptor> results;
results.reserve(names.size()); results.reserve(names.size());
@ -749,7 +755,7 @@ NodeParameters::describe_parameters(const std::vector<std::string> & names) cons
std::vector<uint8_t> std::vector<uint8_t>
NodeParameters::get_parameter_types(const std::vector<std::string> & names) const NodeParameters::get_parameter_types(const std::vector<std::string> & names) const
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
std::vector<uint8_t> results; std::vector<uint8_t> results;
results.reserve(names.size()); results.reserve(names.size());
@ -775,7 +781,7 @@ NodeParameters::get_parameter_types(const std::vector<std::string> & names) cons
rcl_interfaces::msg::ListParametersResult rcl_interfaces::msg::ListParametersResult
NodeParameters::list_parameters(const std::vector<std::string> & prefixes, uint64_t depth) const NodeParameters::list_parameters(const std::vector<std::string> & prefixes, uint64_t depth) const
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::recursive_mutex> lock(mutex_);
rcl_interfaces::msg::ListParametersResult result; rcl_interfaces::msg::ListParametersResult result;
// TODO(mikaelarguedas) define parameter separator different from "/" to avoid ambiguity // TODO(mikaelarguedas) define parameter separator different from "/" to avoid ambiguity
@ -817,6 +823,10 @@ NodeParameters::list_parameters(const std::vector<std::string> & prefixes, uint6
NodeParameters::OnParametersSetCallbackType NodeParameters::OnParametersSetCallbackType
NodeParameters::set_on_parameters_set_callback(OnParametersSetCallbackType callback) NodeParameters::set_on_parameters_set_callback(OnParametersSetCallbackType callback)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex_);
ParameterMutationRecursionGuard guard(parameter_modification_enabled_);
auto existing_callback = on_parameters_set_callback_; auto existing_callback = on_parameters_set_callback_;
on_parameters_set_callback_ = callback; on_parameters_set_callback_ = callback;
return existing_callback; return existing_callback;
@ -832,6 +842,10 @@ NodeParameters::set_on_parameters_set_callback(OnParametersSetCallbackType callb
void void
NodeParameters::register_param_change_callback(ParametersCallbackFunction callback) NodeParameters::register_param_change_callback(ParametersCallbackFunction callback)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex_);
ParameterMutationRecursionGuard guard(parameter_modification_enabled_);
if (on_parameters_set_callback_) { if (on_parameters_set_callback_) {
RCLCPP_WARN( RCLCPP_WARN(
node_logging_->get_logger(), node_logging_->get_logger(),

View file

@ -2148,3 +2148,183 @@ TEST_F(TestNode, get_parameter_types_undeclared_parameters_allowed) {
EXPECT_EQ(results[2], rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET); EXPECT_EQ(results[2], rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET);
} }
} }
// test that it is possible to call get_parameter within the set_callback
TEST_F(TestNode, set_on_parameters_set_callback_get_parameter) {
auto node = std::make_shared<rclcpp::Node>("test_set_callback_get_parameter_node"_unq);
int64_t intval = node->declare_parameter("intparam", 42);
EXPECT_EQ(intval, 42);
double floatval = node->declare_parameter("floatparam", 5.4);
EXPECT_EQ(floatval, 5.4);
double floatout;
RCLCPP_SCOPE_EXIT({node->set_on_parameters_set_callback(nullptr);}); // always reset
auto on_set_parameters =
[&node, &floatout](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (parameters.size() != 1) {
result.successful = false;
}
if (parameters[0].get_value<int>() != 40) {
result.successful = false;
}
rclcpp::Parameter floatparam = node->get_parameter("floatparam");
if (floatparam.get_value<double>() != 5.4) {
result.successful = false;
}
floatout = floatparam.get_value<double>();
return result;
};
EXPECT_EQ(node->set_on_parameters_set_callback(on_set_parameters), nullptr);
ASSERT_NO_THROW(node->set_parameter({"intparam", 40}));
ASSERT_EQ(floatout, 5.4);
}
// test that calling set_parameter inside of a set_callback throws an exception
TEST_F(TestNode, set_on_parameters_set_callback_set_parameter) {
auto node = std::make_shared<rclcpp::Node>("test_set_callback_set_parameter_node"_unq);
int64_t intval = node->declare_parameter("intparam", 42);
EXPECT_EQ(intval, 42);
double floatval = node->declare_parameter("floatparam", 5.4);
EXPECT_EQ(floatval, 5.4);
RCLCPP_SCOPE_EXIT({node->set_on_parameters_set_callback(nullptr);}); // always reset
auto on_set_parameters =
[&node](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (parameters.size() != 1) {
result.successful = false;
}
if (parameters[0].get_value<int>() != 40) {
result.successful = false;
}
// This should throw an exception
node->set_parameter({"floatparam", 5.6});
return result;
};
EXPECT_EQ(node->set_on_parameters_set_callback(on_set_parameters), nullptr);
EXPECT_THROW({
node->set_parameter(rclcpp::Parameter("intparam", 40));
}, rclcpp::exceptions::ParameterModifiedInCallbackException);
}
// test that calling declare_parameter inside of a set_callback throws an exception
TEST_F(TestNode, set_on_parameters_set_callback_declare_parameter) {
auto node = std::make_shared<rclcpp::Node>("test_set_callback_declare_parameter_node"_unq);
int64_t intval = node->declare_parameter("intparam", 42);
EXPECT_EQ(intval, 42);
double floatval = node->declare_parameter("floatparam", 5.4);
EXPECT_EQ(floatval, 5.4);
RCLCPP_SCOPE_EXIT({node->set_on_parameters_set_callback(nullptr);}); // always reset
auto on_set_parameters =
[&node](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (parameters.size() != 1) {
result.successful = false;
}
if (parameters[0].get_value<int>() != 40) {
result.successful = false;
}
// This should throw an exception
node->declare_parameter("floatparam2", 5.6);
return result;
};
EXPECT_EQ(node->set_on_parameters_set_callback(on_set_parameters), nullptr);
EXPECT_THROW({
node->set_parameter(rclcpp::Parameter("intparam", 40));
}, rclcpp::exceptions::ParameterModifiedInCallbackException);
}
// test that calling undeclare_parameter inside a set_callback throws an exception
TEST_F(TestNode, set_on_parameters_set_callback_undeclare_parameter) {
auto node = std::make_shared<rclcpp::Node>("test_set_callback_undeclare_parameter_node"_unq);
int64_t intval = node->declare_parameter("intparam", 42);
EXPECT_EQ(intval, 42);
double floatval = node->declare_parameter("floatparam", 5.4);
EXPECT_EQ(floatval, 5.4);
RCLCPP_SCOPE_EXIT({node->set_on_parameters_set_callback(nullptr);}); // always reset
auto on_set_parameters =
[&node](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (parameters.size() != 1) {
result.successful = false;
}
if (parameters[0].get_value<int>() != 40) {
result.successful = false;
}
// This should throw an exception
node->undeclare_parameter("floatparam");
return result;
};
EXPECT_EQ(node->set_on_parameters_set_callback(on_set_parameters), nullptr);
EXPECT_THROW({
node->set_parameter(rclcpp::Parameter("intparam", 40));
}, rclcpp::exceptions::ParameterModifiedInCallbackException);
}
// test that calling set_on_parameters_set_callback from a set_callback throws an exception
TEST_F(TestNode, set_on_parameters_set_callback_set_on_parameters_set_callback) {
auto node = std::make_shared<rclcpp::Node>("test_set_callback_set_callback_node"_unq);
int64_t intval = node->declare_parameter("intparam", 42);
EXPECT_EQ(intval, 42);
double floatval = node->declare_parameter("floatparam", 5.4);
EXPECT_EQ(floatval, 5.4);
RCLCPP_SCOPE_EXIT({node->set_on_parameters_set_callback(nullptr);}); // always reset
auto on_set_parameters =
[&node](const std::vector<rclcpp::Parameter> & parameters) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
if (parameters.size() != 1) {
result.successful = false;
}
if (parameters[0].get_value<int>() != 40) {
result.successful = false;
}
auto bad_parameters =
[](const std::vector<rclcpp::Parameter> & parameters) {
(void)parameters;
rcl_interfaces::msg::SetParametersResult result;
return result;
};
// This should throw an exception
node->set_on_parameters_set_callback(bad_parameters);
return result;
};
EXPECT_EQ(node->set_on_parameters_set_callback(on_set_parameters), nullptr);
EXPECT_THROW({
node->set_parameter(rclcpp::Parameter("intparam", 40));
}, rclcpp::exceptions::ParameterModifiedInCallbackException);
}