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

Rate support #564

Closed
mattrichard opened this issue Feb 16, 2020 · 6 comments
Closed

Rate support #564

mattrichard opened this issue Feb 16, 2020 · 6 comments

Comments

@mattrichard
Copy link
Collaborator

mattrichard commented Feb 16, 2020

Support for creating a fixed rate is also missing in rclnodejs. This looks like it should be very simple to implement.

ros2/rclpy#443

@wayneparrott
Copy link
Collaborator

wayneparrott commented Mar 2, 2020

A quick thought:

This could be a design challenge for a single threaded javascript implementation based on the rclpy Rate implementation. The classic use-cases for Rate that I'm aware of is an endless loop that uses a Rate to establish a regular frequency for the loop. Following a Rate#sleep() call in this endless loop is a call to spin_once() that fires all active callbacks. At issue is a deadlock that arises when using a Timer in the Rate implementation along with a spin_once() to trigger the Timer's callback.

During Rate#sleep() the rate's timer.callback triggers an event which unblocks clients waiting in Rate#sleep() call. Unfortunately a deadlock situation arises as Timer.callback() will not be invoked unless the containing node is already spinning. Thus if the scenario is using spin_once() as in the code below, the Rate's timer is blocked forever waiting for its callback to be invoked by the spin_once(). But spin_once() will never be called.

import rclpy
from rclpy.executors import SingleThreadedExecutor
context = rclpy.context.Context()
rclpy.init(context=context)
node = rclpy.create_node('test_timer', context=context)
executor = SingleThreadedExecutor(context=context)
executor.add_node(node)
rate = node.create_rate(10)
while rclpy.ok(context=context):
  rate.sleep()
  executor.spin_once(10)
  print('rate test')
print ('done')

@minggangw
Copy link
Member

I read the comment for the sleep() method in rclpy
https://github.com/ros2/rclpy/blob/2b041106ea15d3e2824b00790f7b4b73eb699b4c/rclpy/rclpy/timer.py#L137-L143
It seems that Rate should be used carefully withSingleThreadedExecutor, and I looked through the test case which uses SingleThreadedExecutor
https://github.com/ros2/rclpy/blob/2b041106ea15d3e2824b00790f7b4b73eb699b4c/rclpy/test/test_rate.py#L28, it turns out the sleep() is called on a separate thread. Not sure how the usage of Rate with MultiThreadedExecutor looks like.

@minggangw
Copy link
Member

I came across this article which I think can help us to understand the event loop of Node.js deeply. Hope it's also useful for you 😄

@wayneparrott
Copy link
Collaborator

Thx @minggangw. Assuming that rclnodejs Timer is a key component in the implementation of Rate, there is a need to handle Rate.timer timeout event outside of the current executor model when not spin()'ing.

Alternatively, could you envision a parallel instance of rcl running in its own context either in a worker thread or on the main thread specifically for implementing Rate based on a rcl Timer? Maybe this is goofed thinking. A limitation for this approach is my understanding is that rclnodejs is a singleton which would preclude creating a new context for a Rate timer.

Thoughts?

@minggangw
Copy link
Member

A limitation for this approach is my understanding is that rclnodejs is a singleton which would preclude creating a new context for a Rate timer.

I think the root cause is that Node.js doesn't support multi-thread, e.g. create a sub-thread, semaphore in JS...

We have some limitations when using Node.js, I list some:

  • It has worker_threads, but not well supported as threading in Python and we can only execute some simple tasks on it.
  • It's unusual to block the event loop in Node.js.

So, rclnodejs doesn't have a MultiThreadedExecutor like in rclpy, MultiThreadedExecutor can run the callbacks on different threads, which means the callback of timer used to unlock the rate.sleep() will not be blocked. The situation you described in #564 (comment) somehow is similar to what we meet in Node.js now.

@wayneparrott wayneparrott self-assigned this Mar 5, 2020
@wayneparrott
Copy link
Collaborator

I've got a rate solution that I will submit in a PR soon. The design is really simple in that it runs a timer in a separate rcl context. My prototype seems to work properly with no deadlock when accompanied in a loop with spinOnce().

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

No branches or pull requests

3 participants