ROS 2 rclcpp + rcl - rolling  rolling-20536064
ROS 2 C++ Client Library with ROS Client Library
events_executor.cpp
1 // Copyright 2023 iRobot Corporation.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "rclcpp/experimental/executors/events_executor/events_executor.hpp"
16 
17 #include <memory>
18 #include <utility>
19 #include <vector>
20 
21 #include "rcpputils/compile_warnings.hpp"
22 #include "rcpputils/scope_exit.hpp"
23 
24 using namespace std::chrono_literals;
25 
26 // Disable deprecation warnings while maintaining the EventsExecutor
27 RCPPUTILS_DEPRECATION_WARNING_OFF_START
28 
30 
31 EventsExecutor::EventsExecutor(
32  const rclcpp::ExecutorOptions & options,
33  rclcpp::experimental::executors::EventsQueue::UniquePtr events_queue,
34  bool execute_timers_separate_thread)
35 : rclcpp::Executor(options)
36 {
37  // Get ownership of the queue used to store events.
38  if (!events_queue) {
39  throw std::invalid_argument("events_queue can't be a null pointer");
40  }
41  events_queue_ = std::move(events_queue);
42 
43  // Create timers manager
44  // The timers manager can be used either to only track timers (in this case an expired
45  // timer will generate an executor event and then it will be executed by the executor thread)
46  // or it can also take care of executing expired timers in its dedicated thread.
47  std::function<void(const rclcpp::TimerBase *,
48  const std::shared_ptr<void> &)> timer_on_ready_cb = nullptr;
49  if (!execute_timers_separate_thread) {
50  timer_on_ready_cb =
51  [this](const rclcpp::TimerBase * timer_id, const std::shared_ptr<void> & data) {
52  ExecutorEvent event = {timer_id, data, -1, ExecutorEventType::TIMER_EVENT, 1};
53  this->events_queue_->enqueue(event);
54  };
55  }
56  timers_manager_ =
57  std::make_shared<rclcpp::experimental::TimersManager>(context_, timer_on_ready_cb);
58 
59  entities_need_rebuild_ = false;
60 
61  this->setup_notify_waitable();
62 
63  // Ensure that the entities collection is empty (the base class may have added elements
64  // that we are not interested in)
65  this->current_collection_.clear();
66 
67  // Make sure that the notify waitable is immediately added to the collection
68  // to avoid missing events
69  this->add_notify_waitable_to_collection(current_collection_.waitables);
70 }
71 
72 void
73 EventsExecutor::setup_notify_waitable()
74 {
75  // The base class already created this object but the events-executor
76  // needs different callbacks.
77  assert(notify_waitable_ && "The notify waitable should have already been constructed");
78 
79  notify_waitable_->set_execute_callback(
80  [this]() {
81  // This callback is invoked when:
82  // - the interrupt or shutdown guard condition is triggered:
83  // ---> we need to wake up the executor so that it can terminate
84  // - a node or callback group guard condition is triggered:
85  // ---> the entities collection is changed, we need to update callbacks
86  this->handle_updated_entities(false);
87  });
88 
89  auto notify_waitable_entity_id = notify_waitable_.get();
90  notify_waitable_->set_on_ready_callback(
91  [this, notify_waitable_entity_id](size_t num_events, int waitable_data) {
92  // The notify waitable has a special callback.
93  // We don't care about how many events as when we wake up the executor we are going to
94  // process everything regardless.
95  // For the same reason, if an event of this type has already been pushed but it has not been
96  // processed yet, we avoid pushing additional events.
97  (void)num_events;
98  if (entities_need_rebuild_.exchange(true)) {
99  return;
100  }
101 
102  ExecutorEvent event =
103  {notify_waitable_entity_id, nullptr, waitable_data, ExecutorEventType::WAITABLE_EVENT, 1};
104  this->events_queue_->enqueue(event);
105  });
106 }
107 
109 {
110  cancel_requested_.store(true);
111  notify_waitable_->clear_on_ready_callback();
112  this->refresh_current_collection({});
113 }
114 
115 void
117 {
118  if (spinning.exchange(true)) {
119  throw std::runtime_error("spin() called while already spinning");
120  }
121  RCPPUTILS_SCOPE_EXIT(
122  this->spinning.store(false);
123  this->cancel_requested_.store(false););
124  if (cancel_requested_.load()) {
125  return;
126  }
127 
128  timers_manager_->start();
129  RCPPUTILS_SCOPE_EXIT(timers_manager_->stop(); );
130 
131  while (rclcpp::ok(context_) && !cancel_requested_.load()) {
132  // Wait until we get an event
133  ExecutorEvent event;
134  bool has_event = events_queue_->dequeue(event);
135  if (has_event) {
136  this->execute_event(event);
137  }
138  }
139 }
140 
141 void
142 EventsExecutor::spin_some(std::chrono::nanoseconds max_duration)
143 {
144  return this->spin_some_impl(max_duration, false);
145 }
146 
147 void
148 EventsExecutor::spin_all(std::chrono::nanoseconds max_duration)
149 {
150  if (max_duration <= 0ns) {
151  throw std::invalid_argument("max_duration must be positive");
152  }
153  return this->spin_some_impl(max_duration, true);
154 }
155 
156 void
157 EventsExecutor::spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive)
158 {
159  if (spinning.exchange(true)) {
160  throw std::runtime_error("spin_some() called while already spinning");
161  }
162 
163  RCPPUTILS_SCOPE_EXIT(
164  this->spinning.store(false);
165  this->cancel_requested_.store(false););
166  if (cancel_requested_.load()) {
167  return;
168  }
169 
170  auto start = std::chrono::steady_clock::now();
171 
172  auto max_duration_not_elapsed = [max_duration, start]() {
173  if (std::chrono::nanoseconds(0) == max_duration) {
174  // told to spin forever if need be
175  return true;
176  } else if (std::chrono::steady_clock::now() - start < max_duration) {
177  // told to spin only for some maximum amount of time
178  return true;
179  }
180  // spun too long
181  return false;
182  };
183 
184  // If this spin is not exhaustive (e.g. spin_some), we need to explicitly check
185  // if entities need to be rebuilt here rather than letting the notify waitable event do it.
186  // A non-exhaustive spin would not check for work a second time, thus delaying the execution
187  // of some entities to the next invocation of spin.
188  if (!exhaustive) {
189  this->handle_updated_entities(false);
190  }
191 
192  // Get the number of events and timers ready at start
193  const size_t ready_events_at_start = events_queue_->size();
194  size_t executed_events = 0;
195  const size_t ready_timers_at_start = timers_manager_->get_number_ready_timers();
196  size_t executed_timers = 0;
197 
198  while (rclcpp::ok(context_) && !cancel_requested_.load() && max_duration_not_elapsed()) {
199  // Execute first ready event from queue if exists
200  if (exhaustive || (executed_events < ready_events_at_start)) {
201  bool has_event = !events_queue_->empty();
202 
203  if (has_event) {
204  ExecutorEvent event;
205  bool ret = events_queue_->dequeue(event, std::chrono::nanoseconds(0));
206  if (ret) {
207  this->execute_event(event);
208  executed_events++;
209  continue;
210  }
211  }
212  }
213 
214  // Execute first timer if it is ready
215  if (exhaustive || (executed_timers < ready_timers_at_start)) {
216  bool timer_executed = timers_manager_->execute_head_timer();
217  if (timer_executed) {
218  executed_timers++;
219  continue;
220  }
221  }
222 
223  // If there's no more work available, exit
224  break;
225  }
226 }
227 
228 void
229 EventsExecutor::spin_once_impl(std::chrono::nanoseconds timeout)
230 {
231  // In this context a negative input timeout means no timeout
232  if (timeout < 0ns) {
233  timeout = std::chrono::nanoseconds::max();
234  }
235 
236  // Select the smallest between input timeout and timer timeout.
237  // Cancelled timers are not considered.
238  bool is_timer_timeout = false;
239  auto next_timer_timeout = timers_manager_->get_head_timeout();
240  if (next_timer_timeout.has_value() && next_timer_timeout.value() < timeout) {
241  timeout = next_timer_timeout.value();
242  is_timer_timeout = true;
243  }
244 
245  ExecutorEvent event;
246  bool has_event = events_queue_->dequeue(event, timeout);
247 
248  // If we wake up from the wait with an event, it means that it
249  // arrived before any of the timers expired.
250  if (has_event) {
251  this->execute_event(event);
252  } else if (is_timer_timeout) {
253  timers_manager_->execute_head_timer();
254  }
255 }
256 
257 
258 void
259 EventsExecutor::execute_event(const ExecutorEvent & event)
260 {
261  switch (event.type) {
262  case ExecutorEventType::CLIENT_EVENT:
263  {
264  rclcpp::ClientBase::SharedPtr client;
265  {
266  client = this->retrieve_entity(
267  static_cast<const rcl_client_t *>(event.entity_key),
268  current_collection_.clients);
269  }
270  if (client) {
271  for (size_t i = 0; i < event.num_events; i++) {
272  execute_client(client);
273  }
274  }
275 
276  break;
277  }
278  case ExecutorEventType::SUBSCRIPTION_EVENT:
279  {
280  rclcpp::SubscriptionBase::SharedPtr subscription;
281  {
282  subscription = this->retrieve_entity(
283  static_cast<const rcl_subscription_t *>(event.entity_key),
284  current_collection_.subscriptions);
285  }
286  if (subscription) {
287  for (size_t i = 0; i < event.num_events; i++) {
288  execute_subscription(subscription);
289  }
290  }
291  break;
292  }
293  case ExecutorEventType::SERVICE_EVENT:
294  {
295  rclcpp::ServiceBase::SharedPtr service;
296  {
297  service = this->retrieve_entity(
298  static_cast<const rcl_service_t *>(event.entity_key),
299  current_collection_.services);
300  }
301  if (service) {
302  for (size_t i = 0; i < event.num_events; i++) {
303  execute_service(service);
304  }
305  }
306 
307  break;
308  }
309  case ExecutorEventType::TIMER_EVENT:
310  {
311  timers_manager_->execute_ready_timer(
312  static_cast<const rclcpp::TimerBase *>(event.entity_key), event.data);
313  break;
314  }
315  case ExecutorEventType::WAITABLE_EVENT:
316  {
317  rclcpp::Waitable::SharedPtr waitable;
318  {
319  waitable = this->retrieve_entity(
320  static_cast<const rclcpp::Waitable *>(event.entity_key),
321  current_collection_.waitables);
322  }
323  if (waitable) {
324  for (size_t i = 0; i < event.num_events; i++) {
325  const auto data = waitable->take_data_by_entity_id(event.waitable_data);
326  waitable->execute(data);
327  }
328  }
329  break;
330  }
331  }
332 }
333 
334 void
335 EventsExecutor::handle_updated_entities([[maybe_unused]] bool notify)
336 {
337  // Do not rebuild if we don't need to.
338  // A rebuild event could be generated, but then
339  // this function could end up being called from somewhere else
340  // before that event gets processed, for example if
341  // a node or callback group is manually added to the executor.
342  const bool notify_waitable_triggered = entities_need_rebuild_.exchange(false);
343  if (!notify_waitable_triggered && !this->collector_.has_pending()) {
344  return;
345  }
346 
347  // Build the new collection
349  auto callback_groups = this->collector_.get_all_callback_groups();
351  rclcpp::executors::build_entities_collection(callback_groups, new_collection);
352 
353  // TODO(alsora): this may be implemented in a better way.
354  // We need the notify waitable to be included in the executor "current_collection"
355  // because we need to be able to retrieve events for it.
356  // We could explicitly check for the notify waitable ID when we receive a waitable event
357  // but I think that it's better if the waitable was in the collection and it could be
358  // retrieved in the "standard" way.
359  // To do it, we need to add the notify waitable as an entry in the new collection
360  // such that it's neither added or removed (it should have already been added
361  // to the current collection in the constructor)
362  this->add_notify_waitable_to_collection(new_collection.waitables);
363 
364  this->refresh_current_collection(new_collection);
365 }
366 
367 void
368 EventsExecutor::refresh_current_collection(
369  const rclcpp::executors::ExecutorEntitiesCollection & new_collection)
370 {
371  // Acquire lock before modifying the current collection
372  std::lock_guard<std::mutex> guard(mutex_);
373 
374  // Remove expired entities to ensure re-initialized objects
375  // are updated. This fixes issues with stale state entities.
376  // See: https://github.com/ros2/rclcpp/pull/2586
377  current_collection_.remove_expired_entities();
378 
379  current_collection_.timers.update(
380  new_collection.timers,
381  [this](rclcpp::TimerBase::SharedPtr timer) {timers_manager_->add_timer(timer);},
382  [this](rclcpp::TimerBase::SharedPtr timer) {timers_manager_->remove_timer(timer);});
383 
384  current_collection_.subscriptions.update(
385  new_collection.subscriptions,
386  [this](auto subscription) {
387  subscription->set_on_new_message_callback(
388  this->create_entity_callback(
389  subscription->get_subscription_handle().get(), ExecutorEventType::SUBSCRIPTION_EVENT));
390  },
391  [](auto subscription) {subscription->clear_on_new_message_callback();});
392 
393  current_collection_.clients.update(
394  new_collection.clients,
395  [this](auto client) {
396  client->set_on_new_response_callback(
397  this->create_entity_callback(
398  client->get_client_handle().get(), ExecutorEventType::CLIENT_EVENT));
399  },
400  [](auto client) {client->clear_on_new_response_callback();});
401 
402  current_collection_.services.update(
403  new_collection.services,
404  [this](auto service) {
405  service->set_on_new_request_callback(
406  this->create_entity_callback(
407  service->get_service_handle().get(), ExecutorEventType::SERVICE_EVENT));
408  },
409  [](auto service) {service->clear_on_new_request_callback();});
410 
411  // DO WE NEED THIS? WE ARE NOT DOING ANYTHING WITH GUARD CONDITIONS
412  /*
413  current_collection_.guard_conditions.update(new_collection.guard_conditions,
414  [](auto guard_condition) {(void)guard_condition;},
415  [](auto guard_condition) {guard_condition->set_on_trigger_callback(nullptr);});
416  */
417 
418  current_collection_.waitables.update(
419  new_collection.waitables,
420  [this](auto waitable) {
421  waitable->set_on_ready_callback(
422  this->create_waitable_callback(waitable.get()));
423  for (const auto & t : waitable->get_timers()) {
424  timers_manager_->add_timer(t);
425  }
426  },
427  [this](auto waitable) {
428  waitable->clear_on_ready_callback();
429  for (const auto & t : waitable->get_timers()) {
430  timers_manager_->remove_timer(t);
431  }
432  });
433 }
434 
435 std::function<void(size_t)>
436 EventsExecutor::create_entity_callback(
437  void * entity_key, ExecutorEventType event_type)
438 {
439  std::function<void(size_t)>
440  callback = [this, entity_key, event_type](size_t num_events) {
441  ExecutorEvent event = {entity_key, nullptr, -1, event_type, num_events};
442  this->events_queue_->enqueue(event);
443  };
444  return callback;
445 }
446 
447 std::function<void(size_t, int)>
448 EventsExecutor::create_waitable_callback(const rclcpp::Waitable * entity_key)
449 {
450  std::function<void(size_t, int)>
451  callback = [this, entity_key](size_t num_events, int waitable_data) {
452  ExecutorEvent event =
453  {entity_key, nullptr, waitable_data, ExecutorEventType::WAITABLE_EVENT, num_events};
454  this->events_queue_->enqueue(event);
455  };
456  return callback;
457 }
458 
459 void
460 EventsExecutor::add_notify_waitable_to_collection(
462 {
463  // The notify waitable is not associated to any group, so use an invalid one
464  rclcpp::CallbackGroup::WeakPtr weak_group_ptr;
465  collection.insert(
466  {
467  this->notify_waitable_.get(),
468  {this->notify_waitable_, weak_group_ptr}
469  });
470 }
471 
472 RCPPUTILS_DEPRECATION_WARNING_OFF_STOP
Coordinate the order and timing of available communication tasks.
Definition: executor.hpp:64
std::shared_ptr< rclcpp::Context > context_
The context associated with this executor.
Definition: executor.hpp:589
static RCLCPP_PUBLIC void execute_client(const rclcpp::ClientBase::SharedPtr &client)
Run service client executable.
Definition: executor.cpp:702
static RCLCPP_PUBLIC void execute_service(const rclcpp::ServiceBase::SharedPtr &service)
Run service server executable.
Definition: executor.cpp:690
std::shared_ptr< rclcpp::executors::ExecutorNotifyWaitable > notify_waitable_
Waitable containing guard conditions controlling the executor flow.
Definition: executor.hpp:604
std::atomic_bool cancel_requested_
Tracks a pending cancel request that has not yet been consumed by a spin.
Definition: executor.hpp:578
rclcpp::executors::ExecutorEntitiesCollector collector_
Collector used to associate executable entities from nodes and guard conditions.
Definition: executor.hpp:609
static RCLCPP_PUBLIC void execute_subscription(const rclcpp::SubscriptionBase::SharedPtr &subscription)
Run subscription executable.
Definition: executor.cpp:579
std::atomic_bool spinning
Spinning state, used to prevent multi threaded calls to spin.
Definition: executor.hpp:571
RCLCPP_PUBLIC void update_collections()
Update the underlying collections.
RCLCPP_PUBLIC std::vector< rclcpp::CallbackGroup::WeakPtr > get_all_callback_groups() const
Get all callback groups known to this entity collector.
bool has_pending() const
Indicate if the entities collector has pending additions or removals.
RCLCPP_PUBLIC void spin_once_impl(std::chrono::nanoseconds timeout) override
Internal implementation of spin_once.
RCLCPP_PUBLIC void spin() override
Events executor implementation of spin.
virtual RCLCPP_PUBLIC ~EventsExecutor()
Default destructor.
RCLCPP_PUBLIC void spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive)
Internal implementation of spin_some.
RCLCPP_PUBLIC void spin_all(std::chrono::nanoseconds max_duration) override
Events executor implementation of spin all.
RCLCPP_PUBLIC void handle_updated_entities(bool notify) override
Collect entities from callback groups and refresh the current collection with them.
RCLCPP_PUBLIC void spin_some(std::chrono::nanoseconds max_duration=std::chrono::nanoseconds(0)) override
Events executor implementation of spin some.
Versions of rosidl_typesupport_cpp::get_message_type_support_handle that handle adapted types.
RCLCPP_PUBLIC bool ok(const rclcpp::Context::SharedPtr &context=rclcpp::contexts::get_global_default_context())
Check rclcpp's status.
Structure which encapsulates a ROS Client.
Definition: client.h:43
Structure which encapsulates a ROS Service.
Definition: service.h:43
Structure which encapsulates a ROS Subscription.
Definition: subscription.h:40
Options to be passed to the executor constructor.
Represent the total set of entities for a single executor.
TimerCollection timers
Collection of timers currently in use by the executor.
ServiceCollection services
Collection of services currently in use by the executor.
SubscriptionCollection subscriptions
Collection of subscriptions currently in use by the executor.
WaitableCollection waitables
Collection of waitables currently in use by the executor.
ClientCollection clients
Collection of clients currently in use by the executor.