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