Skip to content
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

Dnae adas/serialized ipm #973

Open
wants to merge 45 commits into
base: rolling
Choose a base branch
from

Conversation

DensoADAS
Copy link
Contributor

Description:
Extended rclcpp to allow serialized intra process communication. Therefore, we added a second connection to the intraprocess manager to the publisher and subscriber for serialized messages. To ease up the memory management, the rcl_serialized_message_t is wrapped in SerializedContainer with a delete to prevent memory loss. To prevent double free the ownership of the serialized message is passed to the publisher. The conversion between serialized and deserialized messages is done in the SubscriptionIntraProcess on demand. The changes also allow general purpose publisher and subscriber as needed e. g. for rosbag2.

Possible applications:
• direct recording of serialized data without serialization (with IPM)
• non-templated publisher of serialized messages (with IPM)

Features:
• added serialization and deserialization functions in intraprocess manager
• implemented publish of serialized messages (call by value and unique pointer) in Publisher and PublisherLifecycle
• claiming ownership of serialized messages by publish(rcl_serialized_message_t)
• create_publisher and create_subscription support now general message type of rcl_serialized_message_t by passing message_type (so non-templated version is possible)
• added unit tests: test_intra_process_communication

@DensoADAS
Copy link
Contributor Author

updated from #819

@DensoADAS
Copy link
Contributor Author

DensoADAS commented Feb 21, 2020

Has anyone reviewed the PR already?
@ivanpauno @wjwwood

@wjwwood
Copy link
Member

wjwwood commented Feb 21, 2020

No, we have not sorry. We'll get to it as soon as we can.

@DensoADAS
Copy link
Contributor Author

DensoADAS commented Apr 16, 2020

@ivanpauno @wjwwood Just a small after-Easter question: Any updates? This PR is blocking the rosbag2 PR.

@ivanpauno
Copy link
Member

@ivanpauno @wjwwood Just a small after-Easter question: Any updates? This PR is blocking the rosbag2 PR.

Sorry, but I don't think that I will be able to do a review before Foxy release.

@Karsten1987 Karsten1987 assigned Karsten1987 and unassigned wjwwood Apr 16, 2020
@Karsten1987
Copy link
Contributor

@DensoADAS can you please rebase this branch? The connected PR for rosbag2 please as well.

Copy link
Contributor

@Karsten1987 Karsten1987 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uff, so that's quite a thing to review :)

I think I understood a fair amount of it to move forward on it.
My main concern would be that the publisher implementation gets too heavily overloaded which makes it quite hard to introspect. What about we introduce a GenericPublisher or SerializedPublisher similar to what we currently have in rosbag2? That would make it a bit easier to distinguish what type of messages are being sent. It also makes it a bit more straight forward to design an API which is tailored to it.

typename PublisherT = rclcpp::Publisher<MessageT, AllocatorT>,
typename NodeT>
std::shared_ptr<PublisherT>
create_publisher(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do I need this overload if both methods are templated by MessageT? The call to const auto type_support = *rosidl_typesupport_cpp::get_message_type_support_handle<MessageT>(); could be done in the original function or even within the create_publisher_factory function, couldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this allows an instantion with type "rcl_serialized_message_t" for generic publishers without previously known message type (e.g. for rosbag2)

Example:
auto publisher = rclcpp::create_publisher<rcl_serialized_message_t>( node, "/topic", *rosidl_typesupport_cpp::get_message_type_support_handle<test_msgs::msg::Strings>(), rclcpp::QoS(10));

rclcpp/include/rclcpp/create_subscription.hpp Show resolved Hide resolved

namespace rclcpp
{
namespace experimental
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why should that be in experimental?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a definition for "experimental"? Should we move it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot the rationale why we introduced "experimental". But I see the Serialization class being easily usable outside of the context of the intraprocess communication. Therefore, I'd propose putting it in the regular rclcpp namespace.

public:
virtual ~SerializationBase() {}

virtual std::shared_ptr<rcl_serialized_message_t> serialize_message(const void * message) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I like that a raw pointer (void *) is getting converted into a shared pointer. What does that mean in terms of ownership? Is the raw pointer no longer owned and directly converted into the shared pointer? What if the message was original allocated on the stack and passed in via &message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I understand your concern. The Serialization class hides the rmw functions. It's difficult to avoid the void* without knowing the actual content (cannot be templated here). For the current PR we skip changing this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the problem here is not passing the message in via a void *. The problem I see here is mainly that it's getting converted to a shared pointer which has implicit assumptions about ownership.

what about changing the signature to something like this:

virtual void serialize_message(const void * ros_message, rcl_serialize_message_t * serialized_message)

input and output are clearly defined and the encapsulation into a shared pointer can be done outside of this funciton.
That also goes along well with the deserialize_message function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message is not converted in this sense. The resulting shared pointer is a new object. (And I want to prevent objects without cleanup functions as much as possible, so I prioritized preventing memory loss)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I had mentioned this somewhere else already, but what about using the SerializedMessage for this?

virtual void serialize_message(const void * ros_message, SerializedMessage & serialized_message);

virtual void deserialize_message(const SerializedMessage & serialized_message, void * ros_message);

Comment on lines 38 to 49
virtual void deserialize_message(
const rcl_serialized_message_t & serialized_message,
void * msg) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the signature of serialize should be the same as deserialize

rclcpp/include/rclcpp/publisher_factory.hpp Show resolved Hide resolved
IntraProcessManager::add_subscription(SubscriptionIntraProcessBase::SharedPtr subscription)
IntraProcessManager::add_subscription(
SubscriptionIntraProcessBase::SharedPtr subscription,
const bool is_serialized)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need that parameter here. It's part of the subscription base: https://github.com/ros2/rclcpp/blob/master/rclcpp/include/rclcpp/subscription_base.hpp#L203-L205

IntraProcessManager::add_publisher(rclcpp::PublisherBase::SharedPtr publisher)
IntraProcessManager::add_publisher(
rclcpp::PublisherBase::SharedPtr publisher,
const bool is_serialized)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see an easy way to set a flag on the publisher base similar to the subscription base which indicates that it's a serialized publisher. Bummer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That being said, what happens if I take the same publisher and publish first a serialized message and then a regular MsgT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: I just browsed through the tests to see what's happening on the user side. What about we don't call a create_publisher function, but rather a create_serialized_publisher which implements the publisherbase interface (sets the is_serialized parameter) and only allows a call to publish with serialized messages.

Comment on lines 229 to 234
// a publisher and a subscription with different content type can't communicate
if (sub_info.is_serialized != pub_info.is_serialized) {
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just when I thought I understood what's happening the intraprocess manager, I read this comment and I am confused again. ;-)
I thought the four different variants are so that a non-serialized subscription could obtain values published from a serialized message?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DensoADAS can you comment on this?

Copy link

@kfabian kfabian Apr 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the point here is that here always the serialized and non-serialized publishers are added to the interprocess manager.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make sense though? Either the publisher is a serialized one or not, am I right?
That is, I think we can just only add one id plus the flag whether it's serialized or not.

@DensoADAS DensoADAS force-pushed the dnae_adas/serialized_ipm branch 2 times, most recently from 9f6d560 to 02571ac Compare April 17, 2020 17:28
@DensoADAS
Copy link
Contributor Author

@Karsten1987 Thank you for your review. I've implemented most of your comments (some stuff like raw pointers need more time) We'd appreciate it if the PR could make it in foxy. Have a nice, healthy and relaxing weekend!

Copy link
Contributor

@Karsten1987 Karsten1987 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another round of review

public:
virtual ~SerializationBase() {}

virtual std::shared_ptr<rcl_serialized_message_t> serialize_message(const void * message) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the problem here is not passing the message in via a void *. The problem I see here is mainly that it's getting converted to a shared pointer which has implicit assumptions about ownership.

what about changing the signature to something like this:

virtual void serialize_message(const void * ros_message, rcl_serialize_message_t * serialized_message)

input and output are clearly defined and the encapsulation into a shared pointer can be done outside of this funciton.
That also goes along well with the deserialize_message function

rclcpp/include/rclcpp/experimental/serialized_message.hpp Outdated Show resolved Hide resolved
rclcpp/include/rclcpp/experimental/serialized_message.hpp Outdated Show resolved Hide resolved

namespace rclcpp
{
namespace experimental
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot the rationale why we introduced "experimental". But I see the Serialization class being easily usable outside of the context of the intraprocess communication. Therefore, I'd propose putting it in the regular rclcpp namespace.

rclcpp/include/rclcpp/experimental/serialization.hpp Outdated Show resolved Hide resolved
@@ -191,34 +192,42 @@ class Publisher : public PublisherBase
get_subscription_count() > get_intra_process_subscription_count();

if (inter_process_publish_needed) {
auto shared_msg = this->do_intra_process_publish_and_return_shared(std::move(msg));
auto shared_msg = this->do_intra_process_publish_and_return_shared(
std::move(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't think we need that line break here.

Comment on lines 229 to 234
// a publisher and a subscription with different content type can't communicate
if (sub_info.is_serialized != pub_info.is_serialized) {
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DensoADAS can you comment on this?

rclcpp/src/rclcpp/subscription_base.cpp Outdated Show resolved Hide resolved
rclcpp/test/test_intra_process_communication.cpp Outdated Show resolved Hide resolved
@DensoADAS
Copy link
Contributor Author

@Karsten1987 another update

@Karsten1987
Copy link
Contributor

Karsten1987 commented Apr 17, 2020

@DensoADAS branch has (still) conflicts

I took the liberty to rebase your branch.
However, when compiling locally on my OSX, I get quite a bunch of compiler errors:

In file included from /Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/src/rclcpp/time_source.cpp:27:
In file included from /Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/node.hpp:37:
In file included from /Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/callback_group.hpp:23:
In file included from /Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/client.hpp:32:
/Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/function_traits.hpp:52:52: error: no member named 'operator()' in 'rclcpp::SubscriptionOptionsWithAllocator<std::__1::allocator<void> >'
    typename function_traits<decltype( &FunctionT::operator())>::arguments>::type;
                                        ~~~~~~~~~~~^
/Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/function_traits.hpp:136:38: note: in instantiation of template class 'rclcpp::function_traits::function_traits<const rclcpp::SubscriptionOptionsWithAllocator<std::__1::allocator<void> > >' requested here
struct function_traits<FunctionT &>: function_traits<FunctionT>
                                     ^
/Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/subscription_traits.hpp:91:39: note: in instantiation of template class 'rclcpp::function_traits::function_traits<const rclcpp::SubscriptionOptionsWithAllocator<std::__1::allocator<void> > &>' requested here
    typename rclcpp::function_traits::function_traits<CallbackT>::template argument_type<0>>
                                      ^
/Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/create_subscription.hpp:43:41: note: in instantiation of template class 'rclcpp::subscription_traits::has_message_type<const rclcpp::SubscriptionOptionsWithAllocator<std::__1::allocator<void> > &, void, void, void>' requested here
  typename rclcpp::subscription_traits::has_message_type<CallbackT>::type,
                                        ^
/Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/create_subscription.hpp:51:1: note: in instantiation of default argument for 'create_subscription<rcl_interfaces::msg::ParameterEvent_<std::__1::allocator<void> >, const rclcpp::SubscriptionOptionsWithAllocator<std::__1::allocator<void> > &, std::__1::allocator<void> >' required here
create_subscription(
^~~~~~~~~~~~~~~~~~~~
/Users/karsten/workspace/ros2/ros2_master/src/ros2/rclcpp/rclcpp/include/rclcpp/parameter_client.hpp:154:12: note: while substituting deduced template arguments into function template 'create_subscription' [with MessageT = rcl_interfaces::msg::ParameterEvent_<std::__1::allocator<void> >, CallbackT = const rclcpp::SubscriptionOptionsWithAllocator<std::__1::allocator<void> > &, AllocatorT = (no value), CallbackMessageT = (no value), SubscriptionT = (no value), MessageMemoryStrategyT = (no value), NodeT = std::__1::shared_ptr<rclcpp::node_interfaces::NodeTopicsInterface> &]
    return rclcpp::create_subscription<rcl_interfaces::msg::ParameterEvent>(
           ^

Joshua Hampp added 9 commits April 17, 2020 13:20
…ge to rcl_serialized_message_t and vice versa

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
… for addind a deleter to ease up memory handling

further features:
 * copy constructor (allowing static memory allocation, e.g. if for static memory allocation in device driver)
 * destructor

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
 * added flag for serialized communication
 * check for flag in "can_communicate"

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
 * distinguish between content type and callback type
 * use "SerializedContainer" for serialized messages for memory deletion
 * added specialized methods for combinations of (un)serialized content/callback
 * automatically (de)serialize messages
 * allowed communication types are now (MessageT==CallbackMessageT || MessageT==SerializedContainer || CallbackMessageT==rcl_serialized_message_t)

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
… serialized communication in publisher base and subscriber base (adapted waitables)

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
 * implemented second communication channel for serialized intra process messages
extended "Publisher" for serialized messages:
 * pass message type by argument
 * added constructor for backwards compatibility (moved common code in separate methods "init_setup")
 * added allocator for serialized messages

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
 * implemented second communication channel for serialized intra process messages
 * extend publish to handle "rcl_serialized_message_t"
 * changed behaviour of serialized message publishing by taking ownership of message (for deletion)

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
…ssage type

 * backwards compatible
 * allows creation of publisher with type "rcl_serialized_message_t"

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
DensoADAS and others added 19 commits April 17, 2020 13:20
Co-Authored-By: Karsten Knese <Karsten1987@users.noreply.github.com>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
* initiazlize variables

Co-Authored-By: Karsten Knese <Karsten1987@users.noreply.github.com>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
 * create_publisher to create_generic_publisher
 * create_subscription to create_generic_subscription

Signed-off-by: Joshua Hampp <j.hampp@denso-adas.de>
@DensoADAS
Copy link
Contributor Author

@Karsten1987 We do not have an osx machine available. From your error log I assume there is a problem with overloading the "create_subscription" function and template deduction. Therefore I renamed the "generic" one (create_generic_subscription/create_generic_publisher). Could you test this? Thank you.

@Karsten1987
Copy link
Contributor

@DensoADAS not sure if you've seen this, but I've opened a PR towards your original branch which addresses this: DensoADAS#1
There is also a second follow-up one, where I tried to summarize the other issues I had with this: DensoADAS#2

@Karsten1987
Copy link
Contributor

So I've just played around with a bit more and I actually think we should re-architecture this PR, maybe break it up into a few smaller ones even.

1.) Introduce a C++ version for the serialization. That includes the rclcpp::SerializedMessage and the rclcpp::Serialization. Additionally, the callback signature should be changed/enhanced to take the C++ class. The need for wrapping this into a shared pointer should vanish with this as well as the destructor of the C++ instance can make sure that the buffer is cleanup correctly.
That work was started in here:
DensoADAS@d6bf983 & DensoADAS@dd65645

2.) Create a template specialization for the publisher, which is instantiated only for rclcpp::SerializedMessage. This should be less impactful on the rclcpp::Publisher, the intra process manager and waitset API, as all the differentiation in the current PR (i.e. std::is_same<T, rcl_serialized_message_t>) can go away. The main motivation for this is that I don't see this scale up if new functionality gets added to the publisher as again the functions have to be cross-checked with a serialized message.
That also removes the need to constantly add two ids to the intra-process manager. This specialization could then further take the typesupport as a argument in the constructor whereas the other publisher does not need to be changed at all.
Work started here: https://gist.github.com/Karsten1987/bae218680ab400292dabd4537e516f64
That specialization has further benefits that all ambiguous member functions can go. In this specialization, there is no need to support a publish to a ROS message or if so, only provide a const void * ros_msg function. Similarly, a borrow_loaned_message can be implemented differently for this specialization - or not at all.
The differentiation template to be used can be done solely in the rclcpp::create_publisher function.
Also, in this case, I'd recommend to deprecate the publish(const rcl_serialized_message_t & msg) function overload on the regular Publisher.

3.) The same thing could technically be done for the subscription class as well. If the user wants to retrieve the data in a serialized way, rclcpp::create_subscription returns a specialized version of it. That doesn't really matter as for now given that we anyway hide this already away, but I think it would ease the change needed for the intra process communication.


The changes in 2.) might have drawbacks as to two different publisher instances have to be used when trying to publish ROS messages vs serialized messages. However, I generally also don't really see a big use-case for this in the first place. I could see how one wants to use one or the other publisher - c.f. rosbags when not being able to depend on the message types during compile time, but never really the mixed version.

@DensoADAS
Copy link
Contributor Author

@Karsten1987 I replaced this PR by 3 new ones

@clalancette clalancette changed the base branch from master to rolling June 28, 2022 14:28
nnmm pushed a commit to ApexAI/rclcpp that referenced this pull request Jul 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants