-
Notifications
You must be signed in to change notification settings - Fork 498
Implemented TODO to make asynchronous destruction of presentation entities #397
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
Changes from all commits
0900065
b405bf5
148ac93
6f9b1f8
00835c3
bf9812f
c90cc9f
230dd3e
bb34f63
eb3b39e
4be2aa5
df19cab
f1a00cb
01040e8
b6e18a6
d2f2d99
3a285f0
813ffee
c025b08
e9377a9
71b9151
d8e690e
cb8f5c3
31fe12f
c470cbc
1a08445
40cacb5
8e799a9
886d7b2
59e95f4
7925086
3dfcb13
4ab7503
0204096
6742854
551772b
4a9e921
c35f767
d747e32
214d7bb
a09d6c7
4b4f04e
c209e8d
b539cb8
4fe7a3d
d607297
eb8f11c
f51d401
12d6c48
372bfc4
a4dca11
a973018
1ad851a
7f057f9
0a69d2b
0d708aa
765d99e
cfa7a1a
3ec3ca8
34d16cb
82177b6
b9377f2
1b94150
0a99f42
9465657
0284932
5e9d6bc
69a94a1
6f2b997
9f9fda9
bc5c75c
5176f62
7cf4ca8
52b9e2a
770cb53
0793514
38bd841
af6b092
7a253d5
28757d0
5fb5ee4
20cc478
4fa6a92
0e0ad04
a5da7a2
5193208
077b80d
daacaec
aab425c
8af7280
1e96de4
7dc156a
36ef981
88d7c4f
2951481
f0a2692
a2bf7ca
38757c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -97,7 +97,10 @@ struct IsFixedPortIdMessageTrait<T, true> | |
| /// Instance of this class is supposed to be created once per transport instance (or even per application). | ||
| /// Main purpose of the presentation object is to create publishers, subscribers, and RPC clients and servers. | ||
| /// | ||
| class Presentation final : private detail::IPresentationDelegate | ||
| /// No Sonar cpp:S4963 'The "Rule-of-Zero" should be followed' | ||
| /// b/c we do directly handle resources here. | ||
| /// | ||
| class Presentation final : private detail::IPresentationDelegate // NOSONAR cpp:S4963 | ||
| { | ||
| public: | ||
| /// @brief Defines failure type of various `make...` methods of the presentation layer. | ||
|
|
@@ -112,7 +115,30 @@ class Presentation final : private detail::IPresentationDelegate | |
| : memory_{memory} | ||
| , executor_{executor} | ||
| , transport_{transport} | ||
| , unreferenced_nodes_{&unreferenced_nodes_, &unreferenced_nodes_} | ||
| { | ||
| unref_nodes_deleter_callback_ = executor_.registerCallback([this](const auto&) { | ||
| // | ||
| destroyUnreferencedNodes(); | ||
| }); | ||
| CETL_DEBUG_ASSERT(unref_nodes_deleter_callback_, "Should not fail b/c we pass proper lambda."); | ||
| } | ||
|
|
||
| Presentation(const Presentation&) = delete; | ||
| Presentation(Presentation&&) noexcept = delete; | ||
| Presentation& operator=(const Presentation&) = delete; | ||
| Presentation& operator=(Presentation&&) noexcept = delete; | ||
|
|
||
| ~Presentation() | ||
| { | ||
| destroyUnreferencedNodes(); | ||
|
|
||
| CETL_DEBUG_ASSERT(shared_client_nodes_.empty(), // | ||
| "RPC clients must be destroyed before presentation."); | ||
| CETL_DEBUG_ASSERT(publisher_impl_nodes_.empty(), // | ||
| "Message publishers must be destroyed before presentation."); | ||
| CETL_DEBUG_ASSERT(subscriber_impl_nodes_.empty(), // | ||
| "Message subscribers must be destroyed before presentation."); | ||
| } | ||
|
|
||
| /// @brief Gets reference to the executor instance of this presentation object. | ||
|
|
@@ -161,6 +187,11 @@ class Presentation final : private detail::IPresentationDelegate | |
| auto* const publisher_impl = std::get<0>(publisher_existing); | ||
| CETL_DEBUG_ASSERT(publisher_impl != nullptr, ""); | ||
|
|
||
| // This publisher impl node might be in the list of previously unreferenced nodes - | ||
| // the ones that are going to be deleted asynchronously (by the `destroyUnreferencedNodes`). | ||
| // If it's the case, we need to remove it from the list b/c it's going to be referenced. | ||
| publisher_impl->unlinkIfReferenced(); | ||
|
|
||
| return Publisher<Message>{publisher_impl}; | ||
| } | ||
|
|
||
|
|
@@ -456,6 +487,8 @@ class Presentation final : private detail::IPresentationDelegate | |
| } | ||
|
|
||
| private: | ||
| using Schedule = IExecutor::Callback::Schedule; | ||
|
|
||
| IPresentationDelegate& asDelegate() noexcept | ||
| { | ||
| return static_cast<IPresentationDelegate&>(*this); | ||
|
|
@@ -517,6 +550,12 @@ class Presentation final : private detail::IPresentationDelegate | |
|
|
||
| auto* const subscriber_impl = std::get<0>(subscriber_existing); | ||
| CETL_DEBUG_ASSERT(subscriber_impl != nullptr, ""); | ||
|
|
||
| // This subscriber impl node might be in the list of previously unreferenced nodes - | ||
| // the ones that are going to be deleted asynchronously (by the `destroyUnreferencedNodes`). | ||
| // If it's the case, we need to remove it from the list b/c it's going to be referenced. | ||
| subscriber_impl->unlinkIfReferenced(); | ||
|
|
||
| return subscriber_impl; | ||
| } | ||
|
|
||
|
|
@@ -571,6 +610,12 @@ class Presentation final : private detail::IPresentationDelegate | |
|
|
||
| auto* const shared_client = std::get<0>(shared_client_existing); | ||
| CETL_DEBUG_ASSERT(shared_client != nullptr, ""); | ||
|
|
||
| // This client impl node might be in the list of previously unreferenced nodes - | ||
| // the ones that are going to be deleted asynchronously (by the `destroyUnreferencedNodes`). | ||
| // If it's the case, we need to remove it from the list b/c it's going to be referenced. | ||
| shared_client->unlinkIfReferenced(); | ||
|
|
||
| return shared_client; | ||
| } | ||
|
|
||
|
|
@@ -628,30 +673,59 @@ class Presentation final : private detail::IPresentationDelegate | |
| } | ||
|
|
||
| template <typename SharedNode> | ||
| void releaseSharedNode(SharedNode* const shared_node, cavl::Tree<SharedNode>& tree) const noexcept | ||
| static void forgetSharedNode(SharedNode& shared_node) noexcept | ||
| { | ||
| CETL_DEBUG_ASSERT(shared_node != nullptr, ""); | ||
| CETL_DEBUG_ASSERT(shared_node.isLinked(), ""); | ||
| CETL_DEBUG_ASSERT(!shared_node.isReferenced(), ""); | ||
|
|
||
| // TODO: make it async (deferred to "on idle" callback). | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the "TODO" this PR is about. |
||
| tree.remove(shared_node); | ||
| shared_node->destroy(); | ||
| // Remove the node from its tree (if it still there), | ||
| // as well as from the list of unreferenced nodes (b/c we gonna finally destroy it). | ||
| shared_node.remove(); // from the tree | ||
| shared_node.unlinkIfReferenced(); // from the list | ||
| } | ||
|
|
||
| void destroyUnreferencedNodes() const noexcept | ||
| { | ||
| // In the loop, destruction of a shared object also removes it from the list of unreferenced nodes. | ||
| // So, it implicitly updates the `unreferenced_nodes_` list. | ||
| while (unreferenced_nodes_.next_node != &unreferenced_nodes_) | ||
| { | ||
| // Downcast is safe here b/c every `UnRefNode` instance is always a `SharedObject` one. | ||
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) | ||
| auto* const shared_obj = static_cast<detail::SharedObject*>(unreferenced_nodes_.next_node); | ||
| shared_obj->destroy(); | ||
| } | ||
| } | ||
|
|
||
| // MARK: IPresentationDelegate | ||
|
|
||
| void releaseSharedClient(detail::SharedClient* const shared_client) noexcept override | ||
| void markSharedObjAsUnreferenced(detail::SharedObject& shared_obj) noexcept override | ||
| { | ||
| // We are not going to destroy the shared object immediately, but schedule it for deletion. | ||
| // This is b/c destruction of shared objects may be time-consuming (f.e. closing under the hood sockets). | ||
| // Double-linked list is used to avoid the need to traverse the tree of shared objects. | ||
| // | ||
| CETL_DEBUG_ASSERT(!shared_obj.isReferenced(), ""); | ||
| shared_obj.linkAsUnreferenced(unreferenced_nodes_); | ||
| // | ||
| const auto result = unref_nodes_deleter_callback_.schedule(Schedule::Once{executor_.now()}); | ||
| CETL_DEBUG_ASSERT(result, "Should not fail b/c we never reset `unref_nodes_deleter_callback_`."); | ||
| (void) result; | ||
| } | ||
|
|
||
| void forgetSharedClient(detail::SharedClient& shared_client) noexcept override | ||
| { | ||
| releaseSharedNode(shared_client, shared_client_nodes_); | ||
| forgetSharedNode(shared_client); | ||
| } | ||
|
|
||
| void releasePublisherImpl(detail::PublisherImpl* const publisher_impl) noexcept override | ||
| void forgetPublisherImpl(detail::PublisherImpl& publisher_impl) noexcept override | ||
| { | ||
| releaseSharedNode(publisher_impl, publisher_impl_nodes_); | ||
| forgetSharedNode(publisher_impl); | ||
| } | ||
|
|
||
| void releaseSubscriberImpl(detail::SubscriberImpl* const subscriber_impl) noexcept override | ||
| void forgetSubscriberImpl(detail::SubscriberImpl& subscriber_impl) noexcept override | ||
| { | ||
| releaseSharedNode(subscriber_impl, subscriber_impl_nodes_); | ||
| forgetSharedNode(subscriber_impl); | ||
| } | ||
|
|
||
| // MARK: Data members: | ||
|
|
@@ -662,6 +736,8 @@ class Presentation final : private detail::IPresentationDelegate | |
| cavl::Tree<detail::SharedClient> shared_client_nodes_; | ||
| cavl::Tree<detail::PublisherImpl> publisher_impl_nodes_; | ||
| cavl::Tree<detail::SubscriberImpl> subscriber_impl_nodes_; | ||
| detail::UnRefNode unreferenced_nodes_; | ||
| IExecutor::Callback::Any unref_nodes_deleter_callback_; | ||
|
|
||
| }; // Presentation | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,9 +25,53 @@ namespace presentation | |
| namespace detail | ||
| { | ||
|
|
||
| /// @brief Defines double-linked list of unreferenced nodes. | ||
| /// | ||
| struct UnRefNode | ||
| { | ||
| void linkAsUnreferenced(UnRefNode& origin) | ||
| { | ||
| CETL_DEBUG_ASSERT((prev_node != nullptr) == (next_node != nullptr), "Should be both or none."); | ||
|
|
||
| // Already linked? | ||
| if ((nullptr == prev_node) && (nullptr == next_node)) | ||
| { | ||
| // Link to the end of the list, so that the object is destroyed in the order of unreferencing. | ||
| // | ||
| next_node = &origin; | ||
| prev_node = origin.prev_node; | ||
| origin.prev_node->next_node = this; | ||
| origin.prev_node = this; | ||
| } | ||
| } | ||
|
|
||
| void unlinkIfReferenced() | ||
| { | ||
| CETL_DEBUG_ASSERT((prev_node != nullptr) == (next_node != nullptr), "Should be both or none."); | ||
|
|
||
| // Already unlinked? | ||
| if ((nullptr != prev_node) && (nullptr != next_node)) | ||
| { | ||
| prev_node->next_node = next_node; | ||
| next_node->prev_node = prev_node; | ||
|
|
||
| next_node = nullptr; | ||
| prev_node = nullptr; | ||
| } | ||
| } | ||
|
|
||
| // No Lint b/c this `UnRefNode` is a simple helper struct as base of the below `SharedObject`. | ||
| // It's under `detail` namespace and not supposed to be used directly by the users of the library. | ||
| // NOLINTBEGIN(misc-non-private-member-variables-in-classes) | ||
| UnRefNode* prev_node{nullptr}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we protect these by making them private then using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we probably can but I'm not sure we need - all derived classes (from |
||
| UnRefNode* next_node{nullptr}; | ||
| // NOLINTEND(misc-non-private-member-variables-in-classes) | ||
|
|
||
| }; // UnRefNode | ||
|
|
||
| /// @brief Defines the base class for all classes that need to be shared (using reference count). | ||
| /// | ||
| class SharedObject | ||
| class SharedObject : public UnRefNode | ||
| { | ||
| public: | ||
| SharedObject() = default; | ||
|
|
@@ -38,6 +82,13 @@ class SharedObject | |
| SharedObject& operator=(const SharedObject&) = delete; | ||
| SharedObject& operator=(SharedObject&&) noexcept = delete; | ||
|
|
||
| /// @brief Gets boolean indicating whether the object is referenced at least once. | ||
| /// | ||
| bool isReferenced() const noexcept | ||
| { | ||
| return ref_count_ > 0; | ||
| } | ||
|
|
||
| /// @brief Increments the reference count. | ||
| /// | ||
| void retain() noexcept | ||
|
|
||
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.
should this be virtual? I'm not sure TBH.
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.
Presentationisfinal, so I don't think we needvirtual.