@@ -497,3 +497,122 @@ TEST_F(TestEventsExecutor, test_default_incompatible_qos_callbacks)
497497
498498 rcutils_logging_set_output_handler (original_output_handler);
499499}
500+
501+ class TestWaitableWithTimer : public rclcpp ::Waitable
502+ {
503+ static constexpr int TimerCallbackType = 0 ;
504+
505+ public:
506+ explicit TestWaitableWithTimer (const rclcpp::Context::SharedPtr & context)
507+ {
508+ auto timer_callback = [this ] () {
509+ timer_ready = true ;
510+ if (ready_callback) {
511+ // inform executor that the waitable is ready to process a timer event
512+ ready_callback (1 , TimerCallbackType);
513+ }
514+ };
515+ timer =
516+ std::make_shared<rclcpp::WallTimer<decltype (timer_callback)>>(std::chrono::milliseconds (10 ),
517+ std::move (timer_callback), context);
518+ }
519+
520+ void
521+ add_to_wait_set (rcl_wait_set_t & /* wait_set*/ ) override {}
522+
523+ bool
524+ is_ready (const rcl_wait_set_t & /* wait_set*/ ) override
525+ {
526+ return false ;
527+ }
528+
529+ std::shared_ptr<void >
530+ take_data () override
531+ {
532+ return std::shared_ptr<void >(nullptr );
533+ }
534+
535+ std::shared_ptr<void >
536+ take_data_by_entity_id (size_t id) override
537+ {
538+ if (id != TimerCallbackType) {
539+ throw std::runtime_error (" Internal error, got wrong ID for take data" );
540+ }
541+ return nullptr ;
542+ }
543+
544+ void
545+ execute (const std::shared_ptr<void > &) override
546+ {
547+ if (timer_ready) {
548+ timer_triggered_waitable_and_waitable_was_executed = true ;
549+ }
550+ }
551+
552+ void
553+ set_on_ready_callback (std::function<void (size_t , int )> callback) override
554+ {
555+ ready_callback = callback;
556+ }
557+
558+ void
559+ clear_on_ready_callback () override
560+ {
561+ ready_callback = std::function<void (size_t , int )>();
562+ }
563+
564+ size_t
565+ get_number_of_ready_guard_conditions () override
566+ {
567+ return 0 ;
568+ }
569+
570+ std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers () const override
571+ {
572+ return {timer};
573+ }
574+
575+ bool timerTriggeredWaitable () const
576+ {
577+ return timer_triggered_waitable_and_waitable_was_executed;
578+ }
579+
580+ private:
581+ std::atomic_bool timer_triggered_waitable_and_waitable_was_executed = false ;
582+ std::atomic_bool timer_ready = false ;
583+ rclcpp::TimerBase::SharedPtr timer;
584+ std::function<void (size_t , int )> ready_callback;
585+ };
586+
587+ TEST_F (TestEventsExecutor, waitable_with_timer)
588+ {
589+ auto node = std::make_shared<rclcpp::Node>(" node" );
590+
591+ auto waitable =
592+ std::make_shared<TestWaitableWithTimer>(rclcpp::contexts::get_global_default_context ());
593+
594+ EventsExecutor executor;
595+ executor.add_node (node);
596+
597+ node->get_node_waitables_interface ()->add_waitable (waitable,
598+ node->get_node_base_interface ()->get_default_callback_group ());
599+
600+ std::thread spinner ([&executor]() {executor.spin ();});
601+
602+ size_t cnt = 0 ;
603+ while (!waitable->timerTriggeredWaitable ()) {
604+ std::this_thread::sleep_for (10ms);
605+ cnt++;
606+
607+ // This should terminate after ~20 ms, so a timeout of 500ms should be plenty
608+ EXPECT_LT (cnt, 51 );
609+ if (cnt > 50 ) {
610+ // timeout case
611+ break ;
612+ }
613+ }
614+ executor.cancel ();
615+ spinner.join ();
616+
617+ EXPECT_TRUE (waitable->timerTriggeredWaitable ());
618+ }
0 commit comments