Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 759: This will add the cleanup service #1236

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions controller_manager/controller_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
set_hardware_component_state,
switch_controllers,
unload_controller,
cleanup_controller,
)

__all__ = [
Expand All @@ -36,4 +37,5 @@
"set_hardware_component_state",
"switch_controllers",
"unload_controller",
"cleanup_controller",
]
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
SetHardwareComponentState,
SwitchController,
UnloadController,
CleanupController,
)

import rclpy
Expand Down Expand Up @@ -175,3 +176,11 @@ def unload_controller(node, controller_manager_name, controller_name, service_ti
request,
service_timeout,
)


def cleanup_controller(node, controller_manager_name, controller_name):
request = CleanupController.Request()
request.name = controller_name
return service_caller(
node, f"{controller_manager_name}/cleanup_controller", CleanupController, request
)
2 changes: 2 additions & 0 deletions controller_manager/controller_manager/spawner.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
load_controller,
switch_controllers,
unload_controller,
cleanup_controller,
)

import rclpy
Expand Down Expand Up @@ -101,6 +102,7 @@ def wait_for_controller_manager(node, controller_manager, timeout_duration):
f"{controller_manager}/reload_controller_libraries",
f"{controller_manager}/switch_controller",
f"{controller_manager}/unload_controller",
f"{controller_manager}/cleanup_controller",
)

# Wait for controller_manager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "controller_manager_msgs/srv/set_hardware_component_state.hpp"
#include "controller_manager_msgs/srv/switch_controller.hpp"
#include "controller_manager_msgs/srv/unload_controller.hpp"
#include "controller_manager_msgs/srv/cleanup_controller.hpp"

#include "diagnostic_updater/diagnostic_updater.hpp"
#include "hardware_interface/handle.hpp"
Expand Down Expand Up @@ -107,6 +108,9 @@ class ControllerManager : public rclcpp::Node
CONTROLLER_MANAGER_PUBLIC
controller_interface::return_type unload_controller(const std::string & controller_name);

CONTROLLER_MANAGER_PUBLIC
controller_interface::return_type cleanup_controller(const std::string & controller_name);

CONTROLLER_MANAGER_PUBLIC
std::vector<ControllerSpec> get_loaded_controllers() const;

Expand Down Expand Up @@ -296,6 +300,11 @@ class ControllerManager : public rclcpp::Node
const std::shared_ptr<controller_manager_msgs::srv::UnloadController::Request> request,
std::shared_ptr<controller_manager_msgs::srv::UnloadController::Response> response);

CONTROLLER_MANAGER_PUBLIC
void cleanup_controller_service_cb(
const std::shared_ptr<controller_manager_msgs::srv::CleanupController::Request> request,
std::shared_ptr<controller_manager_msgs::srv::CleanupController::Response> response);

CONTROLLER_MANAGER_PUBLIC
void list_controller_types_srv_cb(
const std::shared_ptr<controller_manager_msgs::srv::ListControllerTypes::Request> request,
Expand Down Expand Up @@ -521,6 +530,8 @@ class ControllerManager : public rclcpp::Node
switch_controller_service_;
rclcpp::Service<controller_manager_msgs::srv::UnloadController>::SharedPtr
unload_controller_service_;
rclcpp::Service<controller_manager_msgs::srv::CleanupController>::SharedPtr
cleanup_controller_service_;

rclcpp::Service<controller_manager_msgs::srv::ListHardwareComponents>::SharedPtr
list_hardware_components_service_;
Expand Down
130 changes: 109 additions & 21 deletions controller_manager/src/controller_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ rclcpp::QoS qos_services =
.reliable()
.durability_volatile();

inline bool is_controller_unconfigured(
const controller_interface::ControllerInterfaceBase & controller)
{
return controller.get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED;
}

inline bool is_controller_unconfigured(
const controller_interface::ControllerInterfaceBaseSharedPtr & controller)
{
return is_controller_unconfigured(*controller);
}

inline bool is_controller_inactive(const controller_interface::ControllerInterfaceBase & controller)
{
return controller.get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE;
Expand Down Expand Up @@ -504,6 +516,10 @@ void ControllerManager::init_services()
"~/unload_controller",
std::bind(&ControllerManager::unload_controller_service_cb, this, _1, _2), qos_services,
best_effort_callback_group_);
cleanup_controller_service_ = create_service<controller_manager_msgs::srv::CleanupController>(
"~/cleanup_controller",
std::bind(&ControllerManager::cleanup_controller_service_cb, this, _1, _2), qos_services,
best_effort_callback_group_);
list_hardware_components_service_ =
create_service<controller_manager_msgs::srv::ListHardwareComponents>(
"~/list_hardware_components",
Expand Down Expand Up @@ -602,52 +618,109 @@ controller_interface::ControllerInterfaceBaseSharedPtr ControllerManager::load_c
return load_controller(controller_name, controller_type);
}

controller_interface::return_type ControllerManager::unload_controller(
controller_interface::return_type ControllerManager::cleanup_controller(
const std::string & controller_name)
{
std::lock_guard<std::recursive_mutex> guard(rt_controllers_wrapper_.controllers_lock_);
std::vector<ControllerSpec> & to = rt_controllers_wrapper_.get_unused_list(guard);
const std::vector<ControllerSpec> & from = rt_controllers_wrapper_.get_updated_list(guard);
RCLCPP_INFO(get_logger(), "Cleanup controller '%s'", controller_name.c_str());

// Transfers the active controllers over, skipping the one to be removed and the active ones.
to = from;
const auto & controllers = get_loaded_controllers();

auto found_it = std::find_if(
to.begin(), to.end(),
controllers.begin(), controllers.end(),
std::bind(controller_name_compare, std::placeholders::_1, controller_name));
if (found_it == to.end())

if (found_it == controllers.end())
{
// Fails if we could not remove the controllers
to.clear();
RCLCPP_ERROR(
get_logger(),
"Could not unload controller with name '%s' because no controller with this name exists",
"Could not cleanup controller with name '%s' because no controller with this name exists",
controller_name.c_str());
return controller_interface::return_type::ERROR;
}
auto controller = found_it->c;

auto & controller = *found_it;
if (is_controller_unconfigured(controller))
{
// all good nothing to do!
return controller_interface::return_type::OK;
}

if (is_controller_active(*controller.c))
auto state = controller->get_state();
if (
state.id() == lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE ||
state.id() == lifecycle_msgs::msg::State::PRIMARY_STATE_FINALIZED)
{
to.clear();
RCLCPP_ERROR(
get_logger(), "Could not unload controller with name '%s' because it is still active",
controller_name.c_str());
get_logger(), "Controller '%s' can not be cleaned-up from '%s' state.",
controller_name.c_str(), state.label().c_str());
return controller_interface::return_type::ERROR;
}
if (controller.c->is_async())

// ASYNCHRONOUS CONTROLLERS: Stop background thread for update
if (controller->is_async())
{
RCLCPP_DEBUG(
get_logger(), "Removing controller '%s' from the list of async controllers",
controller_name.c_str());
async_controller_threads_.erase(controller_name);
}

// CHAINABLE CONTROLLERS: remove reference interfaces of chainable controllers
if (controller->is_chainable())
{
RCLCPP_DEBUG(
get_logger(),
"Controller '%s' is chainable. Interfaces are being removed from resource manager.",
controller_name.c_str());
resource_manager_->remove_controller_reference_interfaces(controller_name);
}

RCLCPP_DEBUG(get_logger(), "Cleanup controller");
// TODO(destogl): remove reference interface if chainable; i.e., add a separate method for
// cleaning-up controllers?
controller.c->get_node()->cleanup();
auto new_state = controller->get_node()->cleanup();
if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_UNCONFIGURED)
{
RCLCPP_ERROR(
get_logger(), "After cleanup-up, controller '%s' is in state '%s', expected 'unconfigured'.",
controller_name.c_str(), new_state.label().c_str());
return controller_interface::return_type::ERROR;
}

RCLCPP_DEBUG(get_logger(), "Successfully cleaned-up controller '%s'", controller_name.c_str());

return controller_interface::return_type::OK;
}

controller_interface::return_type ControllerManager::unload_controller(
const std::string & controller_name)
{
// first find and clean controller if it is inactive
if (cleanup_controller(controller_name) != controller_interface::return_type::OK)
{
return controller_interface::return_type::ERROR;
}

std::lock_guard<std::recursive_mutex> guard(rt_controllers_wrapper_.controllers_lock_);
std::vector<ControllerSpec> & to = rt_controllers_wrapper_.get_unused_list(guard);
const std::vector<ControllerSpec> & from = rt_controllers_wrapper_.get_updated_list(guard);

to = from;
auto found_it = std::find_if(
to.begin(), to.end(),
std::bind(controller_name_compare, std::placeholders::_1, controller_name));
if (found_it == to.end())
{
// Fails if we could not remove the controllers
to.clear();
RCLCPP_ERROR(
get_logger(),
"Could not unload controller with name '%s' because no controller with this name exists",
controller_name.c_str());
return controller_interface::return_type::ERROR;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the above cleanup controller call should come after this, if not you get the error message of controller not found from cleanup rather than the unload controller


auto & controller = *found_it;

RCLCPP_DEBUG(get_logger(), "Unload controller");
executor_->remove_node(controller.c->get_node()->get_node_base_interface());
to.erase(found_it);

Expand Down Expand Up @@ -723,7 +796,7 @@ controller_interface::return_type ControllerManager::configure_controller(
if (new_state.id() != lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
{
RCLCPP_ERROR(
get_logger(), "After configuring, controller '%s' is in state '%s' , expected inactive.",
get_logger(), "After configuring, controller '%s' is in state '%s', expected 'inactive'.",
controller_name.c_str(), new_state.label().c_str());
return controller_interface::return_type::ERROR;
}
Expand Down Expand Up @@ -1846,6 +1919,21 @@ void ControllerManager::unload_controller_service_cb(
get_logger(), "unloading service finished for controller '%s' ", request->name.c_str());
}

void ControllerManager::cleanup_controller_service_cb(
const std::shared_ptr<controller_manager_msgs::srv::CleanupController::Request> request,
std::shared_ptr<controller_manager_msgs::srv::CleanupController::Response> response)
{
// lock services
RCLCPP_DEBUG(get_logger(), "cleanup service called for controller '%s' ", request->name.c_str());
std::lock_guard<std::mutex> guard(services_lock_);
RCLCPP_DEBUG(get_logger(), "cleanup service locked");

response->ok = cleanup_controller(request->name) == controller_interface::return_type::OK;

RCLCPP_DEBUG(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Print the cleanup finished message if the response of the clean_controller method is OK. If not, better to print an error or leave the earlier error from within the method

get_logger(), "cleanup service finished for controller '%s' ", request->name.c_str());
}

void ControllerManager::list_hardware_components_srv_cb(
const std::shared_ptr<controller_manager_msgs::srv::ListHardwareComponents::Request>,
std::shared_ptr<controller_manager_msgs::srv::ListHardwareComponents::Response> response)
Expand Down
1 change: 1 addition & 0 deletions controller_manager_msgs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ set(srv_files
srv/SetHardwareComponentState.srv
srv/SwitchController.srv
srv/UnloadController.srv
srv/CleanupController.srv
)

rosidl_generate_interfaces(${PROJECT_NAME}
Expand Down
10 changes: 10 additions & 0 deletions controller_manager_msgs/srv/CleanupController.srv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The CleanupController service allows you to cleanup a single controller
# from controller_manager

# To cleanup a controller, specify the "name" of the controller.
# The return value "ok" indicates if the controller was successfully
# cleaned up or not

string name
---
bool ok
40 changes: 40 additions & 0 deletions ros2controlcli/ros2controlcli/verb/cleanup_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2020 PAL Robotics S.L.
#
# 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.

from controller_manager import cleanup_controller

from ros2cli.node.direct import add_arguments
from ros2cli.node.strategy import NodeStrategy
from ros2cli.verb import VerbExtension

from ros2controlcli.api import add_controller_mgr_parsers, LoadedControllerNameCompleter


class CleanupControllerVerb(VerbExtension):
"""Cleanup a controller in a controller manager."""

def add_arguments(self, parser, cli_name):
add_arguments(parser)
arg = parser.add_argument("controller_name", help="Name of the controller")
arg.completer = LoadedControllerNameCompleter()
add_controller_mgr_parsers(parser)

def main(self, *, args):
with NodeStrategy(args) as node:
response = cleanup_controller(node, args.controller_manager, args.controller_name)
if not response.ok:
return "Error cleanup controllers, check controller_manager logs"

print(f"Successfully cleaned up controller {args.controller_name}")
return 0
1 change: 1 addition & 0 deletions ros2controlcli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
ros2controlcli.verb.set_controller_state:SetControllerStateVerb",
"switch_controllers = ros2controlcli.verb.switch_controllers:SwitchControllersVerb",
"unload_controller = ros2controlcli.verb.unload_controller:UnloadControllerVerb",
"cleanup_controller = ros2controlcli.verb.cleanup_controller:CleanupControllerVerb",
],
},
)
Loading