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

Add previously unfinished job manager #20

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Conversation

koonpeng
Copy link
Member

This completes the previous work left halfway https://github.com/osrf/nexus/blob/main/nexus_workcell_orchestrator/src/job.cpp.

It introduces a JobManager to help keep track of tasks, there should be no functionalities differences with the existing code, but it does make unit testing much easier and makes it easier to maintain in the future.

Teo Koon Peng added 5 commits October 11, 2023 14:34
Signed-off-by: Teo Koon Peng <[email protected]>
Signed-off-by: Teo Koon Peng <[email protected]>
Signed-off-by: Teo Koon Peng <[email protected]>
Signed-off-by: Teo Koon Peng <[email protected]>
@koonpeng koonpeng requested a review from Yadunund October 24, 2023 00:58
Copy link
Member

@Yadunund Yadunund left a comment

Choose a reason for hiding this comment

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

In general I think the cleanup is good. Personally I'm not a fan of APIs throwing runtime errors and having to wrap stuff in try-catch blocks. I would prefer if the APIs could rely on returning nullopts or other boolean values indicating unsuccessful executions.

});
if (it != this->_jobs.end())
{
throw JobError("Another task with the same id already exist");
Copy link
Member

Choose a reason for hiding this comment

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

Again to move away from the catch-try pattern, can we return optional<JobPtr> with std::nullopt in this case?

Copy link
Member Author

@koonpeng koonpeng Nov 6, 2023

Choose a reason for hiding this comment

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

I don't mind banning exceptions, but exceptions are used in many places, I think we should do a large scale refactor if that is the direction. Also can you point me to the style guide explaining the use of exceptions? Also would be good to have a link to the style guide in a DEVELOPERS file.

Instead of std::optional, I propose to use std::variant instead. std::optional does not allow the reason for failure to be reported. std::variant would be closest to the way rust does error handling, an alternative is to use std::pair, which would be closer to go.

Copy link
Member

Choose a reason for hiding this comment

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

std::variant works too

"]: Behavior tree returned invalid or unknown status [" <<
bt_status << "]";
RCLCPP_ERROR_STREAM(job.ctx->node->get_logger(), oss.str());
throw std::runtime_error(oss.str());
Copy link
Member

Choose a reason for hiding this comment

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

Thinking about the design decision to throw an exception within JobManager and rely on the workcell orchestrator to catch it handle the error accordingly. Why not directly halt all jobs within the JobManager and not rely on a try-catch block?

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason is because WorkcellOrchestrator may need to know when an error happens (can't rmb if we actually need it currently but having that option is good). May be a good idea to halt + throw exception, I think either is ok, having workcell orchestrator handle the error does make it easier to handle edge cases.

RCLCPP_INFO(this->get_logger(), "Cleaned up");
return CallbackReturn::SUCCESS;
}

auto WorkcellOrchestrator::_configure(
const rclcpp_lifecycle::State& /* previous_state */) -> CallbackReturn
{
this->_ctx_mgr = std::make_shared<ContextManager>();
this->_job_mgr = JobManager(this->shared_from_this(), 1);
Copy link
Member

Choose a reason for hiding this comment

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

Why 1 for max_concurrent? Didn't we have a ROS parameter to set this number?

Copy link
Member Author

@koonpeng koonpeng Nov 6, 2023

Choose a reason for hiding this comment

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

We don't want a workcell to be able to execute multiple tasks at once so it is hardcoded. The option is there so that system orchestrator can reuse the code in the future.

@@ -214,7 +214,7 @@ auto WorkcellOrchestrator::on_activate(
RCLCPP_INFO(this->get_logger(), "Workcell activated");
this->_bt_timer = this->create_wall_timer(BT_TICK_RATE, [this]()
{
this->_tick_all_bts();
this->_job_mgr->tick();
Copy link
Member

Choose a reason for hiding this comment

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

Should we check whether _job_mgr.has_value() before ticking? And same within on_deactive, on_cleanup, etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Using this->_job_mgr.value().tick(); instead, if there is no value it will throw an exception.

Teo Koon Peng added 2 commits November 7, 2023 16:01
@koonpeng koonpeng requested a review from Yadunund November 7, 2023 08:04
Signed-off-by: Luca Della Vedova <[email protected]>
Signed-off-by: Luca Della Vedova <[email protected]>
Copy link
Member

@luca-della-vedova luca-della-vedova left a comment

Choose a reason for hiding this comment

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

Just a point for discussion but this looks good to me

private: std::unique_ptr<lifecycle_manager::LifecycleManager<>> _lifecycle_mgr{
nullptr};
private: std::filesystem::path _bt_path;
// mapping of mapped task type and the original
private: std::unordered_map<std::string, std::string> _task_remaps;
private: TaskParser _task_parser;
private: std::optional<JobManager> _job_mgr;
Copy link
Member

Choose a reason for hiding this comment

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

I can't say I really follow all the different types used for member variables, from what I understand:

  • Normal objects (i.e. TaskParser _task_parser;) for objects that can be default initialized.
  • shared_ptr for objects that can't be initialized in a constructor and are shared with other instances (i.e. the LifecycleNode).
  • unique_ptr for objects that can't be initialized in a constructor and don't need to be shared (i.e. std::unique_ptr<lifecycle_manager::LifecycleManager<>> _lifecycle_mgr{ nullptr};)
  • std::optional introduced here for... An objects that doesn't need to be shared and can't be initialized in a constructor?

It seems to me there is overlap with what can be achieved with std::unique_ptr and std::optional, but using both at the same time is a bit confusing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants