Conversation
1bf8f71 to
e7327f5
Compare
| with self.handle: | ||
| sequence_number = self.__client.send_request(request) |
There was a problem hiding this comment.
@sloretz nit^N: perhaps using the outcome of self.handle.__enter__ is easier to read (though equivalent)?
| with self.handle: | |
| sequence_number = self.__client.send_request(request) | |
| with self.handle as client: | |
| sequence_number = client.send_request(request) |
Same elsewhere.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Aha! Interesting. Good to know.
| cli->node = node; | ||
| // Create a client | ||
| rcl_client_ = std::shared_ptr<rcl_client_t>( | ||
| new rcl_client_t, |
There was a problem hiding this comment.
@sloretz meta: why the mixure of allocation sources? I see PyMem_Alloc, new, and allocator.allocate across the code base.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
rclpy/src/rclpy/client.hpp
Outdated
| /** | ||
| * 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. |
There was a problem hiding this comment.
@sloretz nit^N: I'd drop over the wire:
| * 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.
gbalke
left a comment
There was a problem hiding this comment.
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.
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
8e20909 to
29b08fc
Compare
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
|
CI LGTM, WIndows warning is in cyclonedds |
This converts the Client functions to a C++ class. It includes a couple infrastructure things needed for the conversion
class HandleThis is an RAII wrapper around the
rclpy_handle_ttype. This class can be deleted as soon as all types are no longer usingrclpy_handle_t. When constructed it increments the handle's reference to block destruction, and when destructed it decrements the reference. This allows theClientto keep a node alive, so aClientwon't be destructed after a node.class DestroyableWhile 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 forrclpy_handle_ttypes - 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 makeDestroyableblock new use of itself after destruction is requested, so it gets destroyed as soon as the current uses of it finish.class ClientThis is the conversion to a
Clientclass. It inherits from Destroyable and overridesdestroy()to allow early destruction. It keeps aHandleinstance to the node to prevent it from being destructed before the client. Once aNodeclass is created, that type will becomestd::shared_ptr<rcl_node_t>instead.When
~Client()is called, the destruction order betweenrcl_node_tandrclpy_client_tis guaranteed by thestd::shared_ptr<rcl_client_t>being afternode_handle_in the class definition. WhenClient::destroy()is called, the destruction order is guaranteed by releasing thestd::shared_ptr<rcl_client_t>before releasing thestd::shared_ptr<Handle> node_handle_.