Skip to content

Timers #480

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

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

Timers #480

wants to merge 12 commits into from

Conversation

mxgrey
Copy link
Collaborator

@mxgrey mxgrey commented May 14, 2025

This PR splits the timer.rs module out of #446 so it can be reviewed separately after #446 is merged.

@mxgrey mxgrey mentioned this pull request May 14, 2025
Signed-off-by: Michael X. Grey <[email protected]>
@mxgrey mxgrey force-pushed the worker_with_timers branch from a2af202 to 2d20c4e Compare June 29, 2025 06:16
mxgrey added 6 commits June 29, 2025 15:55
Signed-off-by: Michael X. Grey <[email protected]>
Signed-off-by: Michael X. Grey <[email protected]>
Signed-off-by: Michael X. Grey <[email protected]>
Signed-off-by: Michael X. Grey <[email protected]>
Signed-off-by: Michael X. Grey <[email protected]>
@mxgrey
Copy link
Collaborator Author

mxgrey commented Jun 29, 2025

@esteve This is embarrassing since I was vocally supportive of committing the cargo lockfiles, but now I'm thinking that might have been a mistake. I'm noticing two issues in practice:

  1. Every run of colcon build seems to update the lockfiles. In fact a bunch of different tools that I use on a regular basis are triggering automatic updates of the lockfiles. It's creating a lot of unnecessary churn in the git history.
  2. More crucially, I'm seeing this error happen in the minimum supported Rust compiler version CI. Essentially any new lockfiles that get generated and committed will be incompatible with the "Minimal" CI.

I've had to resort to manually deleting the Cargo.lock for the worker_demo, logging_demo, and now timer_demo examples each time I commit in order to get the Minimal CI to pass.

I'd suggest we remove the lockfiles from the git history or else this is going to cause a great deal of ongoing annoyance where everyone is constantly reverting lockfiles before committing 😞

Signed-off-by: Michael X. Grey <[email protected]>
@mxgrey
Copy link
Collaborator Author

mxgrey commented Jun 29, 2025

@knmcguire Sorry to summon you into this random PR but it seems a new CI failure has popped up for Windows.

I have a feeling this is due to this recent PR in colcon-cargo, and we'll see this failure show up for the main branch on Tuesday during its weekly CI run.

@knmcguire
Copy link
Contributor

knmcguire commented Jun 30, 2025

Thanks for letting me know ! I don't get messages of the CI failures on windows so I was unaware of this. I'll dig into it and make a PR

The cargo lock did solve some things for the windows CI though so if that is removed we might need to deal with those failures, but let's see when it comes.

@knmcguire
Copy link
Contributor

See this PR: #495

Copy link
Collaborator

@jhdcs jhdcs left a comment

Choose a reason for hiding this comment

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

It looks very good for the most part! I just noticed some unsafe calls that didn't have Safety comments associated with them.

/// Gets the period of the timer
pub fn get_timer_period(&self) -> Result<Duration, RclrsError> {
let mut timer_period_ns = 0;
unsafe {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?

/// Checks whether the timer is canceled or not
pub fn is_canceled(&self) -> Result<bool, RclrsError> {
let mut is_canceled = false;
unsafe {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?

/// Retrieves the time since the last call to the callback
pub fn time_since_last_call(&self) -> Result<Duration, RclrsError> {
let mut time_value_ns: i64 = 0;
unsafe {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?

/// Retrieves the time until the next call of the callback
pub fn time_until_next_call(&self) -> Result<Duration, RclrsError> {
let mut time_value_ns: i64 = 0;
unsafe {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?

/// Resets the timer.
pub fn reset(&self) -> Result<(), RclrsError> {
let mut rcl_timer = self.handle.rcl_timer.lock().unwrap();
unsafe { rcl_timer_reset(&mut *rcl_timer) }.ok()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?


/// Checks if the timer is ready (not canceled)
pub fn is_ready(&self) -> Result<bool, RclrsError> {
let is_ready = unsafe {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?

/// except to keep track of when the timer has been called.
fn rcl_call(&self) -> Result<(), RclrsError> {
let mut rcl_timer = self.handle.rcl_timer.lock().unwrap();
unsafe { rcl_timer_call(&mut *rcl_timer) }.ok()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?

}

impl<Scope: WorkScope> RclPrimitive for TimerExecutable<Scope> {
unsafe fn execute(&mut self, payload: &mut dyn Any) -> Result<(), RclrsError> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this have a safety comment?

Copy link
Contributor

@knmcguire knmcguire left a comment

Choose a reason for hiding this comment

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

Managed to get this demo to run on my machine (ubuntu on wsl2) 😄

I'm not comfortable to do a full indepth review yet... but I've added some thoughts.

name = "examples_timer_demo"
version = "0.1.0"
edition = "2021"

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe good to have a bin here so that we can run it through ROS2 command?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is this what you had in mind?

Since the example is in src/main.rs, by default it will generate a binary with the same name as the package, examples_timer_demo. But I see in the other example packages there's a certain naming pattern for the binaries, so now I've copied that pattern here.

Copy link
Contributor

Choose a reason for hiding this comment

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

ahhh gotcha.

And yes exactly like that. naming consistency is good :)

let _timer = worker.create_timer_repeating(timer_period, move |count: &mut usize| {
*count += 1;
println!(
"Drinking 🧉 for the {}th time every {:?}.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Works for me and is a nice example :)

Does ROS2 have an example like this actually? Not used in the tutorials right?

I guess the only nitpick would be, that all other python/cpp examples use create_timer()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See this reply.

@@ -893,6 +895,214 @@ impl NodeState {
)
}

/// Create a [`Timer`] with a repeating callback.
Copy link
Contributor

Choose a reason for hiding this comment

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

I was first confused on why there were some much commented out lines, but then I realized that this was for the documentation. Nice!

/// The executor needs to be [spinning][1] for a timer's callback to be triggered.
///
/// Timers can be created by a [`Node`] using one of these methods:
/// - [`NodeState::create_timer_repeating`][2]
Copy link
Contributor

Choose a reason for hiding this comment

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

This goes a bit too deep for my knowledge I'm afraid. But are these timer naming's consistent with the api of rclcpp or rclpy ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The naming is intentionally not the same because in rclrs we're introducing the concept of a one-shot timer, which is different from a repeating timer.

The type system of the Rust language makes a strong distinction between the traits FnMut() and FnOnce(). Repeating timers can use callbacks with the FnMut() trait but cannot use callbacks with the FnOnce() trait. One-shot timers are able to use callbacks with the FnOnce() trait, but we have to guarantee that the callback will only get triggered one time, and then after that the timer must be inert (not contain any callback).

Because of this we end up with three possible kinds of timers:

  • Repeating
  • One-shot
  • Inert

rclcpp and rclpy don't have this distinction because they have no concept of FnMut versus FnOnce, and I don't think they even support a concept of one-shot timer, although the subject has been discussed before.

In my mind this is the kind of divergence that is expected between the client libraries of different languages, in order to reflect differences in what those languages offer.

Copy link
Contributor

Choose a reason for hiding this comment

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

aha.... yes

In hindsight I can indeed see quite differences between the c++ and python tutorials as there are some differences with that as well. Then focusing on api naming consistency shouldn't be the goal as long as it is feature complete.

thanks for the explanation! I missed/forgot this.

Signed-off-by: Michael X. Grey <[email protected]>
@mxgrey
Copy link
Collaborator Author

mxgrey commented Jul 17, 2025

@jhdcs thanks for noticing the absence of safety comments. I've added them in via a589997

@mxgrey
Copy link
Collaborator Author

mxgrey commented Jul 17, 2025

CI is currently failing because of an upstream issue which is fixed by ros2-rust/rosidl_runtime_rs#13

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