ROS 2 rclcpp + rcl - rolling  rolling-e7363f10
ROS 2 C++ Client Library with ROS Client Library
scheduler.hpp
1 // Copyright 2024 Cellumation GmbH.
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 #pragma once
15 
16 #include <algorithm>
17 #include <condition_variable>
18 #include <deque>
19 #include <functional>
20 #include <list>
21 #include <memory>
22 #include <mutex>
23 #include <utility>
24 
25 #include <rclcpp/callback_group.hpp>
26 #include "global_event_id_provider.hpp"
27 
28 namespace rclcpp
29 {
30 namespace executors
31 {
32 namespace cbg_executor
33 {
34 struct Worker
35 {
36  std::mutex mutex;
37  std::condition_variable condition_variable;
38  std::atomic<bool> wakeup = false;
39 
40  void prepare_block()
41  {
42  wakeup = false;
43  }
44 
45  void block()
46  {
47  std::unique_lock lk(mutex);
48  condition_variable.wait(lk, [this]() -> bool {
49  return wakeup;
50  });
51  }
52 
53  void block_for(std::chrono::nanoseconds timeout)
54  {
55  std::unique_lock lk(mutex);
56  condition_variable.wait_for(lk, timeout, [this]() -> bool {
57  return wakeup;
58  });
59  }
60 
67  void unblock()
68  {
69  {
70  std::unique_lock lk(mutex);
71  wakeup = true;
72  }
73  condition_variable.notify_one();
74  }
75 
76  /*
77  * This function shall be used if there is a risk that the worker
78  * might get deleted after wakeup. This function is supposed to
79  * be used in the shutdown case.
80  * This function is less performant as the unblock function, as
81  * the notofied thread may be tempoarlily blocked again until the
82  * mutex is released.
83  */
84  void unblock_thread_safe()
85  {
86  std::unique_lock lk(mutex);
87  wakeup = true;
88  condition_variable.notify_one();
89  }
90 };
91 
102 {
103  std::mutex workers_mutex;
104  std::deque<Worker *> workers;
105 
106  bool release_workers = false;
107 
113  {
114  if(workers.empty()) {
115  // no threads available
116  return nullptr;
117  }
118  Worker * worker = workers.front();
119  workers.pop_front();
120  return worker;
121  }
122 
128  {
129  if(release_workers) {
130  return false;
131  }
132  workers.push_front(worker);
133  return true;
134  }
135 
142  {
143  auto it = std::find(workers.begin(), workers.end(), worker);
144  if(it != workers.end()) {
145  workers.erase(it);
146  }
147  }
148 
154  std::deque<Worker *> release_all_worker_threads()
155  {
156  std::deque<Worker *> cpy;
157  cpy.swap(workers);
158  release_workers = true;
159  return cpy;
160  }
161 };
162 
164 {
165 public:
167  {
168  rclcpp::Waitable::WeakPtr waitable;
169  int internal_event_type;
170 
171  bool expired() const
172  {
173  return waitable.expired();
174  }
175  };
176 
178  {
179  explicit CallbackEventType(std::function<void()> callback)
180  : callback(std::move(callback))
181  {
182  }
183 
184  std::function<void()> callback;
185 
186  bool expired() const
187  {
188  return false;
189  }
190  };
191 
193  {
194  explicit CallbackGroupHandle(CBGScheduler & scheduler, CallbackGroupType type)
195  : scheduler(scheduler), type(type)
196  {
197  }
198 
199  CallbackGroupHandle(const CallbackGroupHandle &) = delete;
201 
202  virtual ~CallbackGroupHandle() = default;
203 
204  CallbackGroupHandle & operator=(const CallbackGroupHandle &) = delete;
205  CallbackGroupHandle & operator=(CallbackGroupHandle &&) = delete;
206 
207  virtual std::function<void(size_t)> get_ready_callback_for_entity(
208  const rclcpp::SubscriptionBase::WeakPtr & entity) = 0;
209  virtual std::function<void(std::function<void()> executed_callback)>
210  get_ready_callback_for_entity(const rclcpp::TimerBase::WeakPtr & entity) = 0;
211  virtual std::function<void(size_t)> get_ready_callback_for_entity(
212  const rclcpp::ClientBase::WeakPtr & entity) = 0;
213  virtual std::function<void(size_t)> get_ready_callback_for_entity(
214  const rclcpp::ServiceBase::WeakPtr & entity) = 0;
215  virtual std::function<void(size_t,
216  int)> get_ready_callback_for_entity(const rclcpp::Waitable::WeakPtr & entity) = 0;
217  virtual std::function<void(size_t)> get_ready_callback_for_entity(
218  const CallbackEventType & entity) = 0;
219 
224  {
225  {
226  std::lock_guard l(ready_mutex);
227  not_ready = false;
228 
229  if(!has_ready_entities()) {
230  idle = true;
231  return;
232  }
233  }
234  // inform scheduler that we have more work
235  scheduler.callback_group_ready(this, false);
236  }
237 
238  CallbackGroupType get_type() {return type;}
239 
240  bool is_ready();
241 
242  // true if this cbg is inside the scheduler's queue
243  bool in_queue = false;
244 
245 protected:
246  CBGScheduler & scheduler;
247 
251  virtual bool has_ready_entities() const = 0;
252 
260  template<typename add_fun>
261  void add_ready_entity(const add_fun & fun)
262  {
263  {
264  std::lock_guard l(ready_mutex);
265 
266  fun();
267 
268  if(not_ready || !idle) {
269  return;
270  }
271 
272  idle = false;
273  }
274 
275  // If we reached this point, we were idle and now have work,
276  // therefore we need to move this callback group into the list
277  // of ready callback groups.
278  scheduler.callback_group_ready(this, true);
279  }
280 
281  void mark_as_skipped()
282  {
283  if(!has_ready_entities()) {
284  idle = true;
285  }
286  }
287 
294  {
295  if (type != CallbackGroupType::Reentrant) {
296  not_ready = true;
297  }
298  }
299 
300  std::mutex ready_mutex;
301 
302 private:
303  // will be set if cbg is mutual exclusive and something is executing
304  bool not_ready = false;
305 
306  // true, if nothing is beeing executed, and there are no pending events
307  bool idle = true;
308 
309  // type of the underlying callback group
310  CallbackGroupType type;
311  };
312 
314  {
315  // if called executes the entity
316  std::function<void()> execute_function;
317  // The callback group associated with the entity. Can be nullptr.
318  CallbackGroupHandle *callback_handle = nullptr;
319  };
320 
326  explicit CBGScheduler(std::function<void ()> sync_function)
327  : sync_function(sync_function) {}
328  CBGScheduler(const CBGScheduler &) = delete;
329  CBGScheduler(CBGScheduler &&) = delete;
330  virtual ~CBGScheduler() = default;
331 
332  CBGScheduler & operator=(const CBGScheduler &) = delete;
333  CBGScheduler & operator=(CBGScheduler &&) = delete;
334 
335  CallbackGroupHandle * add_callback_group(const rclcpp::CallbackGroup::SharedPtr & callback_group)
336  {
337  auto uPtr = get_handle_for_callback_group(callback_group);
338  CallbackGroupHandle * ret = uPtr.get();
339 
340  std::lock_guard lk(ready_callback_groups_mutex);
341 
342  callback_groups.push_back(std::move(uPtr));
343  return ret;
344  }
345 
346  void remove_callback_group(const CallbackGroupHandle *callback_handle)
347  {
348  std::lock_guard lk(ready_callback_groups_mutex);
349 
350  auto cbg_it = std::find(ready_callback_groups.begin(),
351  ready_callback_groups.end(), callback_handle);
352  if (cbg_it != ready_callback_groups.end()) {
353  ready_callback_groups.erase(cbg_it);
354  }
355 
356  callback_groups.remove_if([&callback_handle] (const auto & e) {
357  return e.get() == callback_handle;
358  });
359  }
360 
367  void callback_group_ready(CallbackGroupHandle *handle, bool callback_group_was_idle)
368  {
369  {
370  std::lock_guard l(ready_callback_groups_mutex);
371 
372  if (!handle->in_queue) {
373  ready_callback_groups.push_back(handle);
374  handle->in_queue = true;
375  }
376  }
377 
378  if(callback_group_was_idle) {
380  }
381  }
382 
384  {
385  std::optional<ExecutableEntity> entity;
386  bool moreEntitiesReady{};
387  };
388 
396  {
397  std::lock_guard l(ready_callback_groups_mutex);
398  worker_checking_for_work = false;
399 
400  if(needs_sync) {
401  needs_sync = false;
402  return ExecutableEntityWithInfo{.entity =
403  ExecutableEntity{.execute_function = sync_function, .callback_handle = nullptr},
404  .moreEntitiesReady = false};
405  }
406 
408  }
409 
410  ExecutableEntityWithInfo get_next_ready_entity(
411  GlobalEventIdProvider::MonotonicId max_id)
412  {
413  std::lock_guard l(ready_callback_groups_mutex);
414  worker_checking_for_work = false;
415 
416  if(needs_sync) {
417  needs_sync = false;
418  return ExecutableEntityWithInfo{.entity =
419  ExecutableEntity{.execute_function = sync_function, .callback_handle = nullptr},
420  .moreEntitiesReady = false};
421  }
422 
423  return get_next_ready_entity_intern(max_id);
424  }
425 
431  GlobalEventIdProvider::MonotonicId max_id) = 0;
432 
442  {
443  bool wake_worker = false;
444  {
445  std::lock_guard l(ready_callback_groups_mutex);
446  if(!needs_sync) {
447  wake_worker = true;
448  }
449  needs_sync = true;
450  }
451  if(wake_worker) {
453  }
454  }
455 
462  {
463  if(e.callback_handle != nullptr) {
464  e.callback_handle->mark_as_executed();
465  }
466  }
467 
474  {
475  std::lock_guard l(ready_callback_groups_mutex);
476  worker_checking_for_work = true;
477  }
478 
485  {
486  Worker * worker = nullptr;
487  {
488  std::scoped_lock l(ready_callback_groups_mutex, worker_queue.workers_mutex);
489  if(worker_checking_for_work) {
490  return;
491  }
492  worker = worker_queue.pop_blocked_worker_thread();
493  if(worker == nullptr) {
494  return;
495  }
496  worker_checking_for_work = true;
497  }
498  worker->unblock();
499  }
500 
509  {
510  if(!prepare_and_enqueue_worker(worker)) {
511  return;
512  }
513  worker->block();
514  }
515 
516  void block_worker_thread_for(Worker * worker, std::chrono::nanoseconds timeout)
517  {
518  if(!prepare_and_enqueue_worker(worker)) {
519  return;
520  }
521  worker->block_for(timeout);
522  // on timeout the worker is still in the queue, remove it, as the
523  // worker may not be valid any more after this call
524  std::lock_guard lk(worker_queue.workers_mutex);
525  worker_queue.remove_worker_thread(worker);
526  }
527 
528  void release_all_worker_threads()
529  {
530  std::deque<Worker *> workers;
531  {
532  std::lock_guard lk(worker_queue.workers_mutex);
533  workers = worker_queue.release_all_worker_threads();
534  }
535  for(Worker * worker : workers) {
536  worker->unblock_thread_safe();
537  }
538  }
539 
540 protected:
541  virtual std::unique_ptr<CallbackGroupHandle> get_handle_for_callback_group(
542  const rclcpp::CallbackGroup::SharedPtr & callback_group) = 0;
543 
549  {
550  worker->prepare_block();
551  std::scoped_lock l(ready_callback_groups_mutex, worker_queue.workers_mutex);
552  if(needs_sync || !ready_callback_groups.empty()) {
553  return false;
554  }
555  return worker_queue.push_blocked_worker_thread(worker);
556  }
557 
558  // sync function, will be triggered if the executor needs
559  // resync. E.g. if entities / cbg or nodes were added / removed
560  std::function<void ()> sync_function;
561 
562  bool needs_sync = false;
563 
564  std::mutex ready_callback_groups_mutex;
565  std::deque<CallbackGroupHandle *> ready_callback_groups;
566 
567  bool worker_checking_for_work = false;
568 
569  WorkerQueue worker_queue;
570 
571  std::list<std::unique_ptr<CallbackGroupHandle>> callback_groups;
572 };
573 } // namespace cbg_executor
574 } // namespace executors
575 } // namespace rclcpp
void callback_group_ready(CallbackGroupHandle *handle, bool callback_group_was_idle)
Definition: scheduler.hpp:367
virtual ExecutableEntityWithInfo get_next_ready_entity_intern()=0
void mark_entity_as_executed(const ExecutableEntity &e)
Definition: scheduler.hpp:461
ExecutableEntityWithInfo get_next_ready_entity()
Definition: scheduler.hpp:395
CBGScheduler(std::function< void()> sync_function)
Definition: scheduler.hpp:326
Versions of rosidl_typesupport_cpp::get_message_type_support_handle that handle adapted types.
std::deque< Worker * > release_all_worker_threads()
Definition: scheduler.hpp:154