-
Notifications
You must be signed in to change notification settings - Fork 235
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
Introduce EventsExecutor implementation #1391
Open
bmartin427
wants to merge
32
commits into
ros2:rolling
Choose a base branch
from
bmartin427:events_executor
base: rolling
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
45f7f5f
Introduce EventsExecutor implementation (#1389)
18036c4
Fix explosion for reference count updates without GIL
ebb1ee3
Fix rclpy triggering its own deprecation warnings
68ecfb4
Fix ament linter complaints
9722f08
Switch to non-boost asio library
1570aac
Use internal _rclpy C++ types instead of jumping through Python hoops
99fdd9f
Reformat with clang-format, then uncrustify again
3631594
Respect init signal handling options
f68f995
Reconcile signal handling differences with SingleThreadedExecutor
447df5e
test_executor.py: Add coverage for EventsExecutor
4a26313
Make spin_once() only return after a user-visible callback
9e95b53
Add get_nodes() method
99f6f93
Add context management support
dde4442
Remove mutex protection on nodes_ member access
aabfc18
Fix deadlock during shutdown()
4a181d8
A little further on using C++ _rclpy types instead of Python interface
7cc7f5f
Fix issue with iterating through incomplete Tasks
3502872
Add support for coroutines to timer handling
4dd05c6
Fix test_not_lose_callback() test to destroy entities properly
776a1a3
Correct test that wasn't running at all, and remove EventsExecutor fr…
8bfbfbd
Warn on every timer added over the threshold, not just the first
bmartin427 126c754
Keep rcl_timer_call() tightly coupled with user callback dispatch
ee14711
Protect against deferred method calls happening against a deleted Clo…
35f4aea
Add support for new TimerInfo data passed to timer handlers
df3dbb7
Simplify spin_once() implementation
168c5cc
Fix stale Future done callbacks with spin_until_future_complete()
b9e5240
Use existing rclpy signal handling instead of asio
0959658
Replace asio timers with a dedicated timer wait thread
8f764df
Correct busy-looping in async callback handling
a9c21c6
Replace asio::io_context with a new EventsQueue object
c3f0714
Merge remote-tracking branch 'origin/rolling' into events_executor
6856054
Add EventsExecutor to new test_executor test from merge
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Copyright 2024-2025 Brad Martin | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from .events_executor import EventsExecutor # noqa: F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Copyright 2024-2025 Brad Martin | ||
# Copyright 2024 Merlin Labs, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
import faulthandler | ||
import typing | ||
|
||
import rclpy.executors | ||
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy | ||
import rclpy.node | ||
|
||
|
||
# Try to look like we inherit from the rclpy Executor for type checking purposes without | ||
# getting any of the code from the base class. | ||
def EventsExecutor(*, context: rclpy.Context | None = None) -> rclpy.executors.Executor: | ||
if context is None: | ||
context = rclpy.get_default_context() | ||
|
||
# For debugging purposes, if anything goes wrong in C++ make sure we also get a | ||
# Python backtrace dumped with the crash. | ||
faulthandler.enable() | ||
|
||
ex = typing.cast(rclpy.executors.Executor, _rclpy.EventsExecutor(context)) | ||
|
||
# rclpy.Executor does this too. Note, the context itself is smart enough to check | ||
# for bound methods, and check whether the instances they're bound to still exist at | ||
# callback time, so we don't have to worry about tearing down this stale callback at | ||
# destruction time. | ||
# TODO(bmartin427) This should really be done inside of the EventsExecutor | ||
# implementation itself, but I'm unable to figure out a pybind11 incantation that | ||
# allows me to pass this bound method call from C++. | ||
context.on_shutdown(ex.wake) | ||
|
||
return ex |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright 2025 Brad Martin | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
#include "events_executor/delayed_event_thread.hpp" | ||
|
||
#include <utility> | ||
|
||
namespace rclpy | ||
{ | ||
namespace events_executor | ||
{ | ||
|
||
DelayedEventThread::DelayedEventThread(EventsQueue * events_queue) | ||
: events_queue_(events_queue), thread_([this]() {RunThread();}) | ||
{ | ||
} | ||
|
||
DelayedEventThread::~DelayedEventThread() | ||
{ | ||
{ | ||
std::unique_lock<std::mutex> lock(mutex_); | ||
done_ = true; | ||
} | ||
cv_.notify_one(); | ||
thread_.join(); | ||
} | ||
|
||
void DelayedEventThread::EnqueueAt( | ||
std::chrono::steady_clock::time_point when, std::function<void()> handler) | ||
{ | ||
{ | ||
std::unique_lock<std::mutex> lock(mutex_); | ||
when_ = when; | ||
handler_ = handler; | ||
} | ||
cv_.notify_one(); | ||
} | ||
|
||
void DelayedEventThread::RunThread() | ||
{ | ||
std::unique_lock<std::mutex> lock(mutex_); | ||
while (!done_) { | ||
if (handler_) { | ||
// Make sure we don't dispatch a stale wait if it changes while we're waiting. | ||
const auto latched_when = when_; | ||
if ( | ||
(std::cv_status::timeout == cv_.wait_until(lock, latched_when)) && handler_ && | ||
(when_ <= latched_when)) | ||
{ | ||
auto handler = std::move(handler_); | ||
handler_ = {}; | ||
events_queue_->Enqueue(std::move(handler)); | ||
} | ||
} else { | ||
// Wait indefinitely until we get signaled that there's something worth looking at. | ||
cv_.wait(lock); | ||
} | ||
} | ||
} | ||
|
||
} // namespace events_executor | ||
} // namespace rclpy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright 2025 Brad Martin | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
#pragma once | ||
|
||
#include <chrono> | ||
#include <condition_variable> | ||
#include <functional> | ||
#include <mutex> | ||
#include <thread> | ||
|
||
#include "events_executor/events_queue.hpp" | ||
|
||
namespace rclpy | ||
{ | ||
namespace events_executor | ||
{ | ||
|
||
/// This object manages posting an event handler to an EventsQueue after a specified delay. The | ||
/// current delay may be changed or canceled at any time. This is done by way of a self-contained | ||
/// child thread to perform the wait. | ||
class DelayedEventThread | ||
{ | ||
public: | ||
/// The pointer is aliased and must live for the lifetime of this object. | ||
explicit DelayedEventThread(EventsQueue *); | ||
~DelayedEventThread(); | ||
|
||
/// Schedules an event handler to be enqueued at the specified time point. Replaces any previous | ||
/// wait and handler, which will never be dispatched if it has not been already. | ||
void EnqueueAt(std::chrono::steady_clock::time_point when, std::function<void()> handler); | ||
|
||
/// Cancels any previously-scheduled handler. | ||
void Cancel() {EnqueueAt({}, {});} | ||
|
||
private: | ||
void RunThread(); | ||
|
||
EventsQueue * const events_queue_; | ||
std::mutex mutex_; | ||
bool done_{}; | ||
std::condition_variable cv_; | ||
std::chrono::steady_clock::time_point when_; | ||
std::function<void()> handler_; | ||
std::thread thread_; | ||
}; | ||
|
||
} // namespace events_executor | ||
} // namespace rclpy |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly this is not allowed in the ros code base, as it's not part of the c++ standard