-
Notifications
You must be signed in to change notification settings - Fork 502
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
single_threaded_executor uses moved reference #402
Comments
I can't help but think this shouldn't work. line 219 links addresses in stack memory into the AVL tree? |
I understand your concerns but it all was done thoughtful and purpose. Please see move constructor/assignment of CAVL node, specifically its BTW, following to your concern, please take a look also at examples |
This violates the Principle of Least Astonishment. C++ is supposed to be expressive in a way that makes the programmer's intent visible. In this case we end up with something that reads: CETL_NODISCARD Callback::Any registerCallback(Callback::Function&& function) override
{
CallbackNode new_cb_node{*this, std::move(function)}; //< create an automatic object on the stack.
insertCallbackNode(new_cb_node); //< insert that object into a member data structure
return {std::move(new_cb_node)}; //< move the object, we just inserted into the local data structure, out of this object.
} even more Astonishing: void insertCallbackNode(CallbackNode& callback_node)
{
const auto next_exec_time = callback_node.nextExecTime();
const std::tuple<CallbackNode*, bool> cb_node_existing = callback_nodes_.search( //
[next_exec_time](const CallbackNode& other_node) { // predicate
//
return other_node.compareByExecutionTime(next_exec_time);
},
[&callback_node]() { return &callback_node; }); // "factory"
(void) cb_node_existing;
} in the above code What's further Astonishing is if you try to customize this callback node (as I did). For example: libcyphal::IExecutor::Callback::Any FreeRTOSQueueExecutor::registerCallbackWithMemo(
libcyphal::IExecutor::Callback::Function&& function, libcyphal::IExecutor::Callback::MemoType memo) {
CustomCallbackNode new_cb_node{*this, std::move(function), memo};
insertCallbackNode(new_cb_node);
return {std::move(new_cb_node)};
} then I do this: FreeRTOSQueueExecutor::SpinResult QueueExecutor::spinOnce() {
...
while (auto* const callback_node_ptr = callback_nodes_.min()) {
auto& callback_node = *callback_node_ptr;
...
// The memo, passed into the CustomCallbackNode constructor, is always 0x00
if (callback_node.getMemo() == 0x01) {
callback_node.schedule(libcyphal::IExecutor::Callback::Schedule::Once{now()});
}
}
... or, if I do what one should do with an template <typename... Args>
libcyphal::IExecutor::Callback::Any registerCallbackInternal(Args&&... args) {
libcyphal::IExecutor::Callback::Any cavl_node_any{
libcyphal::IExecutor::Callback::Any::in_place_type_t<CustomCallbackNode>{}, std::forward<Args>(args)...};
CustomCallbackNode* cavl_node_actually = cetl::get_if<CustomCallbackNode>(&cavl_node_any);
insertCallbackNode(cavl_node_actually);
return cavl_node_any;
}
libcyphal::IExecutor::Callback::Any FreeRTOSQueueExecutor::registerCallbackWithMemo(
libcyphal::IExecutor::Callback::Function&& function, libcyphal::IExecutor::Callback::MemoType memo) {
return registerCallbackInternal(*this, std::move(function), memo);
} now it does explode when run. Finally, I may not be the smartest engineer on the planet but I have been writing C++ for over 20 years and I still can't figure out how the CAVL code is safe and correct. |
You do make a valid point that the solution is somewhat unconventional for C++. I think such a strong reliance on moving is more likely to be found in, say, Rust, where move-rich approaches are encouraged by its ownership model. The design you are looking at is an attempt to meet the high-level design goal mentioned earlier by applying foreign concepts not often found in C++ codebases. Perhaps we should try and clean up the rough edges in this design before undoing it entirely in favor of the more conventional but less appealing pmr-based design? |
How would we do this? Again, I'm still not clear how the pointer-to-stack-memory is okay in the CAVL search/insert operation. If I could understand what's going on in that method then I'd be able to comment on how we would make this less Astonishing. |
a. calls b. as part of the above ctor Node's move constructor is called , where c. destructor of Node@0x1000 is called - does nothing b/c its pointers are all nullptr - so called "not linked" node. d. return to the caller of If later whole |
I agree with what you said about "Principle of Least Astonishment" only partially. |
@thirtytwobits can we give your |
@thirtytwobits @pavel-kirienko |
On line 109 the callback node object is linked into the local CAVL tree. On line 110 that same object is moved into an
Any
that is returned from theregisterCallback
. This means the executor is working with moved-from memory. Not sure why this works?The text was updated successfully, but these errors were encountered: