Skip to content

Convert Client to use C++ Class#739

Merged
sloretz merged 4 commits intomasterfrom
pybind11_class_client
Mar 30, 2021
Merged

Convert Client to use C++ Class#739
sloretz merged 4 commits intomasterfrom
pybind11_class_client

Conversation

@sloretz
Copy link
Copy Markdown
Contributor

@sloretz sloretz commented Mar 26, 2021

This converts the Client functions to a C++ class. It includes a couple infrastructure things needed for the conversion

class Handle

This is an RAII wrapper around the rclpy_handle_t type. This class can be deleted as soon as all types are no longer using rclpy_handle_t. When constructed it increments the handle's reference to block destruction, and when destructed it decrements the reference. This allows the Client to keep a node alive, so a Client won't be destructed after a node.

class Destroyable

While refactoring I found while std::shared_ptr<> solves the destruction order issue, there's still a need to block destruction of an object while it's in use. This class implements that very similarly to how it's done for rclpy_handle_t types - though I did choose to make a design change.

When destruction is requested on an rclpy_handle_t, new threads are allowed to use it as long as there is another thread using it. I didn't like that it meant two very fast threads using it back and forth (with overlap in their use) could block destruction forever. I chose to make Destroyable block new use of itself after destruction is requested, so it gets destroyed as soon as the current uses of it finish.

class Client

This is the conversion to a Client class. It inherits from Destroyable and overrides destroy() to allow early destruction. It keeps a Handle instance to the node to prevent it from being destructed before the client. Once a Node class is created, that type will become std::shared_ptr<rcl_node_t> instead.

When ~Client() is called, the destruction order between rcl_node_t and rclpy_client_t is guaranteed by the std::shared_ptr<rcl_client_t> being after node_handle_ in the class definition. When Client::destroy() is called, the destruction order is guaranteed by releasing the std::shared_ptr<rcl_client_t> before releasing the std::shared_ptr<Handle> node_handle_.

@sloretz sloretz requested review from gbalke, hidmic and ivanpauno March 26, 2021 18:12
@sloretz sloretz self-assigned this Mar 26, 2021
@sloretz
Copy link
Copy Markdown
Contributor Author

sloretz commented Mar 26, 2021

CI (build: --packages-above-and-dependencies rclpy test: --packages-above rclpy)

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

@sloretz
Copy link
Copy Markdown
Contributor Author

sloretz commented Mar 26, 2021

CI (build: --packages-above-and-dependencies rclpy test: --packages-above rclpy)

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

@sloretz sloretz force-pushed the pybind11_class_client branch from 1bf8f71 to e7327f5 Compare March 29, 2021 16:44
@sloretz
Copy link
Copy Markdown
Contributor Author

sloretz commented Mar 29, 2021

CI (build: --packages-above-and-dependencies rclpy test: --packages-above rclpy)

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Copy link
Copy Markdown
Contributor

@hidmic hidmic left a comment

Choose a reason for hiding this comment

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

LGTM

Comment on lines +125 to +126
with self.handle:
sequence_number = self.__client.send_request(request)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sloretz nit^N: perhaps using the outcome of self.handle.__enter__ is easier to read (though equivalent)?

Suggested change
with self.handle:
sequence_number = self.__client.send_request(request)
with self.handle as client:
sequence_number = client.send_request(request)

Same elsewhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I went the opposite direction in a6149d1 and made the context manager return None when it's entered. I'm a little cautions because I don't know if pybind11 has a way to figure out the Destroyable type returned at __enter__ is an instance of rclpy::Client. If __enter__ returns Destroyable d, then is client.send_request effectively reinterpret_cast<Client *>(d)->send_request()? I think I recall @mxgrey showing me a case (IIRC involving multiple inheritance) where that's unsafe to do.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Aha! Interesting. Good to know.

cli->node = node;
// Create a client
rcl_client_ = std::shared_ptr<rcl_client_t>(
new rcl_client_t,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sloretz meta: why the mixure of allocation sources? I see PyMem_Alloc, new, and allocator.allocate across the code base.

Copy link
Copy Markdown
Contributor

@gbalke gbalke Mar 29, 2021

Choose a reason for hiding this comment

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

Why not use the smart pointer library to allocate the object? http://www.cplusplus.com/reference/memory/make_shared/

Edit: I see you've used it above. Not sure if it is just not usable in this context?

Edit2: Also make_shared makes 4 different allocation sources hehe.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

why the mixure of allocation sources?

Laziness. new throws if allocation fails, so the code doesn't have to check PyMem_Malloc's return or cleanup the shared_ptr to prevent attempting to free it twice. Maybe a PyMem_Malloc wrapper that throws on failure would be convenient here.

Why not use the smart pointer library to allocate the object? http://www.cplusplus.com/reference/memory/make_shared/

I didn't use make_shared because I don't think it allows a custom deleter.

/**
* This function will create a client for the given service name.
* This client will use the typesupport defined in the service module
* provided as pysrv_type to send messages over the wire.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sloretz nit^N: I'd drop over the wire:

Suggested change
* provided as pysrv_type to send messages over the wire.
* provided as pysrv_type to send messages.

We're careful elsewhere not to give details about the transport. Perhaps we should be everywhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed mention of transport in 8e20909

Copy link
Copy Markdown
Contributor

@gbalke gbalke left a comment

Choose a reason for hiding this comment

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

Looks good. I am enjoying the scoping benefits of classes 😄.

I am a little curious about the "not-virtual" virtual destroy function but I see that it's simply a quirk of pybind11. https://pybind11.readthedocs.io/en/stable/advanced/classes.html

I guess since this is intended to be bound immediately, there's no good reason to make a PyDestroyable wrapper.

sloretz added 3 commits March 29, 2021 16:34
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
@sloretz sloretz force-pushed the pybind11_class_client branch from 8e20909 to 29b08fc Compare March 29, 2021 23:34
@sloretz
Copy link
Copy Markdown
Contributor Author

sloretz commented Mar 30, 2021

I'll take a second pass at the new/delete use in a follow up PR.

CI (build: --packages-above-and-dependencies rclpy test: --packages-above rclpy)

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
@sloretz
Copy link
Copy Markdown
Contributor Author

sloretz commented Mar 30, 2021

Fixed Clang warning, running CI just to rclpy since it already passed with --packages-above-and-dependencies and the change seems small enough to not need a re-run of everything downstream

(build: --packages-up-to rclpy test: --packages-select rclpy)

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

@sloretz
Copy link
Copy Markdown
Contributor Author

sloretz commented Mar 30, 2021

CI LGTM, WIndows warning is in cyclonedds

@sloretz sloretz merged commit 91be548 into master Mar 30, 2021
@delete-merged-branch delete-merged-branch bot deleted the pybind11_class_client branch March 30, 2021 22:15
@sloretz sloretz mentioned this pull request Mar 31, 2021
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.

3 participants