Alternative way of giving timers a clock#212
Conversation
1) Pass clock into rclpy_create_timer 2) Make use of pycapsule destructor
rclpy/rclpy/node.py
Outdated
| if tmr.timer_handle == timer.timer_handle: | ||
| _rclpy.rclpy_destroy_entity(tmr.timer_handle) | ||
| # TODO(sloretz) Store clocks on node and destroy them separately | ||
| _rclpy.rclpy_destroy_entity(tmr._clock._clock_handle) |
There was a problem hiding this comment.
A ROSTime clock would need to be fed from a time source that is tied to the node. Clocks may need to be gotten off a Node instance, so it would make sense for that instance to store and destroy them later.
| [self.timer_handle, self.timer_pointer, _] = _rclpy.rclpy_create_timer(timer_period_ns) | ||
| # TODO(sloretz) Allow passing clocks in via timer constructor | ||
| self._clock = Clock(clock_type=ClockType.STEADY_TIME) | ||
| [self.timer_handle, self.timer_pointer] = _rclpy.rclpy_create_timer( |
There was a problem hiding this comment.
The returned clock was unused. Since a ROSTime clock may need to be retrieved off a Node it makes sense to pass the clock in, and reuse the clock creation code in Clock.
|
|
||
| /// Destructor for a clock | ||
| void | ||
| _rclpy_destroy_clock(PyObject * pycapsule) |
There was a problem hiding this comment.
Moving this above to reuse it in rclpy_destroy_entity
| PyMem_Free(clock); | ||
| } else if (PyCapsule_IsValid(pyentity, "rcl_clock_t")) { | ||
| PyCapsule_SetDestructor(pyentity, NULL); | ||
| _rclpy_destroy_clock(pyentity); |
There was a problem hiding this comment.
The pycapsule destructor will fini the clock if the user doesn't. If the clock is destroyed explicitly then fini the clock, but set the destructor to NULL so the garbage collector doesn't do it again.
|
@dirk-thomas if the changes look OK to you feel free to merge this PR. It's targeted at branch |
dirk-thomas
left a comment
There was a problem hiding this comment.
The change makes sense to me. Inline just a few minor comments. Can you also trigger a CI build for it?
rclpy/src/rclpy/_rclpy.c
Outdated
| PyObject * pyclock; | ||
|
|
||
| if (!PyArg_ParseTuple(args, "K", &period_nsec)) { | ||
| if (!PyArg_ParseTuple(args, "KO", &period_nsec, &pyclock)) { |
There was a problem hiding this comment.
Should the clock be based first and the period second (as in the other languages)?
| void | ||
| _rclpy_destroy_clock(PyObject * pycapsule) | ||
| { | ||
| rcl_clock_t * clock = (rcl_clock_t *)PyCapsule_GetPointer(pycapsule, "rcl_clock_t"); |
There was a problem hiding this comment.
The code should check for !clock.
There was a problem hiding this comment.
So currently it silently returns?
There was a problem hiding this comment.
Not silent, it raises an exception. There's no return because the function signature is PyCapsule_Destructor.
There was a problem hiding this comment.
Sorry, I still don't understand it. If clock is NULL that means a wrong parameter was passed - not a rcl_clock_t capsule. How is this early return different from the case where clock is not null and correctly finalized?
There was a problem hiding this comment.
How is this early return different from the case where clock is not null and correctly finalized?
If PyCapsule_GetPointer() returns NULL then it raised an exception. If the clock is correctly finalized then no exception is raised.
If clock is NULL that means a wrong parameter was passed
I don't see a way to pass a wrong parameter to this function at the moment. It shouldn't get the wrong type of pointer when using it as a capsule destructor.
rclpy/rclpy/src/rclpy/_rclpy.c
Line 3184 in 5ed7ca2
And the new call in this PR calls PyCapsule_IsValid() first. PyCapsule_GetPointer() is guaranteed not to return NULL if that passes.
There was a problem hiding this comment.
Thanks for the detailed description. I guess I was kind of confused by the comment - I updated it in a new commit: a6934ca
rclpy/rclpy/node.py
Outdated
| if tmr.timer_handle == timer.timer_handle: | ||
| _rclpy.rclpy_destroy_entity(tmr.timer_handle) | ||
| # TODO(sloretz) Store clocks on node and destroy them separately | ||
| _rclpy.rclpy_destroy_entity(tmr._clock._clock_handle) |
There was a problem hiding this comment.
Instead of accessing a "protected" member (tmr._clock) should this be exposed through a getter?
|
@dirk-thomas CI testing |
Fixed in ros2/ci#212 (comment) |
* create timer with clock * fix wrong PyMem_Free argument * fix clock cleanup * sync with master * Alternative way of giving timers a clock (#212) * Alternative way of giving timers a clock 1) Pass clock into rclpy_create_timer 2) Make use of pycapsule destructor
I was having trouble putting feedback for #211 into words. This passes the clock into
rclpy_create_timer()instead of it being returned in an unused variable, and makes use of the cleanup code in_rclpy_destroy_clock().