Skip to content

Commit 36ef981

Browse files
committed
Implemented TODO to make asynchronous destruction of presentation entities (publishers, subscribers and rpc clients).
Now unreferenced shared objects (with ref_count==0) will be quickly appended to the double-linked list, which will be traversed asynchronously in a near future callback.
1 parent 7dc156a commit 36ef981

File tree

12 files changed

+483
-237
lines changed

12 files changed

+483
-237
lines changed

include/libcyphal/presentation/client_impl.hpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ namespace detail
3737
class SharedClient : public cavl::Node<SharedClient>, public SharedObject
3838
{
3939
public:
40+
using Node::remove;
41+
using Node::isLinked;
42+
4043
class TimeoutNode : public Node<TimeoutNode>
4144
{
4245
public:
@@ -229,13 +232,20 @@ class SharedClient : public cavl::Node<SharedClient>, public SharedObject
229232
CETL_DEBUG_ASSERT(cb_nodes_by_transfer_id_.empty(), "");
230233
CETL_DEBUG_ASSERT(timeout_nodes_by_deadline_.empty(), "");
231234

232-
delegate_.releaseSharedClient(this);
235+
delegate_.markSharedObjAsUnreferenced(*this);
233236
return true;
234237
}
235238

236239
return false;
237240
}
238241

242+
// MARK: SharedObject
243+
244+
void destroy() noexcept override
245+
{
246+
delegate_.forgetSharedClient(*this);
247+
}
248+
239249
protected:
240250
virtual void insertNewCallbackNode(CallbackNode& callback_node)
241251
{
@@ -393,7 +403,7 @@ class SharedClient : public cavl::Node<SharedClient>, public SharedObject
393403
/// @brief Defines a shared client implementation that uses a generic transfer ID generator.
394404
///
395405
template <typename TransferIdGeneratorMixin>
396-
class ClientImpl final : public SharedClient, public TransferIdGeneratorMixin
406+
class ClientImpl final : public SharedClient, private TransferIdGeneratorMixin
397407
{
398408
public:
399409
ClientImpl(IPresentationDelegate& delegate,
@@ -410,10 +420,13 @@ class ClientImpl final : public SharedClient, public TransferIdGeneratorMixin
410420

411421
void destroy() noexcept override
412422
{
423+
Base::destroy();
413424
destroyWithPmr(this, memory());
414425
}
415426

416427
private:
428+
using Base = SharedClient;
429+
417430
// MARK: SharedClient
418431

419432
CETL_NODISCARD cetl::optional<transport::TransferId> nextTransferId() noexcept override
@@ -440,25 +453,28 @@ class ClientImpl final : public SharedClient, public TransferIdGeneratorMixin
440453
template <>
441454
class ClientImpl<transport::detail::TrivialTransferIdGenerator> final
442455
: public SharedClient,
443-
public transport::detail::TrivialTransferIdGenerator
456+
private transport::detail::TrivialTransferIdGenerator
444457
{
445458
public:
446459
ClientImpl(IPresentationDelegate& delegate,
447460
IExecutor& executor,
448461
UniquePtr<transport::IRequestTxSession> svc_request_tx_session,
449462
UniquePtr<transport::IResponseRxSession> svc_response_rx_session)
450-
: SharedClient{delegate, executor, std::move(svc_request_tx_session), std::move(svc_response_rx_session)}
463+
: Base{delegate, executor, std::move(svc_request_tx_session), std::move(svc_response_rx_session)}
451464
{
452465
}
453466

454467
// MARK: SharedObject
455468

456469
void destroy() noexcept override
457470
{
471+
Base::destroy();
458472
destroyWithPmr(this, memory());
459473
}
460474

461475
private:
476+
using Base = SharedClient;
477+
462478
// MARK: SharedClient
463479

464480
CETL_NODISCARD cetl::optional<transport::TransferId> nextTransferId() noexcept override

include/libcyphal/presentation/presentation.hpp

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ struct IsFixedPortIdMessageTrait<T, true>
9797
/// Instance of this class is supposed to be created once per transport instance (or even per application).
9898
/// Main purpose of the presentation object is to create publishers, subscribers, and RPC clients and servers.
9999
///
100-
class Presentation final : private detail::IPresentationDelegate
100+
/// No Sonar cpp:S4963 'The "Rule-of-Zero" should be followed'
101+
/// b/c we do directly handle resources here.
102+
///
103+
class Presentation final : private detail::IPresentationDelegate // NOSONAR cpp:S4963
101104
{
102105
public:
103106
/// @brief Defines failure type of various `make...` methods of the presentation layer.
@@ -112,7 +115,30 @@ class Presentation final : private detail::IPresentationDelegate
112115
: memory_{memory}
113116
, executor_{executor}
114117
, transport_{transport}
118+
, unreferenced_nodes_{&unreferenced_nodes_, &unreferenced_nodes_}
119+
{
120+
unref_nodes_deleter_callback_ = executor_.registerCallback([this](const auto&) {
121+
//
122+
destroyUnreferencedNodes();
123+
});
124+
CETL_DEBUG_ASSERT(unref_nodes_deleter_callback_, "Should not fail b/c we pass proper lambda.");
125+
}
126+
127+
Presentation(const Presentation&) = delete;
128+
Presentation(Presentation&&) noexcept = delete;
129+
Presentation& operator=(const Presentation&) = delete;
130+
Presentation& operator=(Presentation&&) noexcept = delete;
131+
132+
~Presentation()
115133
{
134+
destroyUnreferencedNodes();
135+
136+
CETL_DEBUG_ASSERT(shared_client_nodes_.empty(), //
137+
"RPC clients must be destroyed before presentation.");
138+
CETL_DEBUG_ASSERT(publisher_impl_nodes_.empty(), //
139+
"Message publishers must be destroyed before presentation.");
140+
CETL_DEBUG_ASSERT(subscriber_impl_nodes_.empty(), //
141+
"Message subscribers must be destroyed before presentation.");
116142
}
117143

118144
/// @brief Gets reference to the executor instance of this presentation object.
@@ -161,6 +187,11 @@ class Presentation final : private detail::IPresentationDelegate
161187
auto* const publisher_impl = std::get<0>(publisher_existing);
162188
CETL_DEBUG_ASSERT(publisher_impl != nullptr, "");
163189

190+
// This publisher impl node might be in the list of previously unreferenced nodes -
191+
// the ones that are going to be deleted asynchronously (by the `destroyUnreferencedNodes`).
192+
// If it's the case, we need to remove it from the list b/c it's going to be referenced.
193+
publisher_impl->unlinkIfReferenced();
194+
164195
return Publisher<Message>{publisher_impl};
165196
}
166197

@@ -456,6 +487,8 @@ class Presentation final : private detail::IPresentationDelegate
456487
}
457488

458489
private:
490+
using Schedule = IExecutor::Callback::Schedule;
491+
459492
IPresentationDelegate& asDelegate() noexcept
460493
{
461494
return static_cast<IPresentationDelegate&>(*this);
@@ -517,6 +550,12 @@ class Presentation final : private detail::IPresentationDelegate
517550

518551
auto* const subscriber_impl = std::get<0>(subscriber_existing);
519552
CETL_DEBUG_ASSERT(subscriber_impl != nullptr, "");
553+
554+
// This subscriber impl node might be in the list of previously unreferenced nodes -
555+
// the ones that are going to be deleted asynchronously (by the `destroyUnreferencedNodes`).
556+
// If it's the case, we need to remove it from the list b/c it's going to be referenced.
557+
subscriber_impl->unlinkIfReferenced();
558+
520559
return subscriber_impl;
521560
}
522561

@@ -571,6 +610,12 @@ class Presentation final : private detail::IPresentationDelegate
571610

572611
auto* const shared_client = std::get<0>(shared_client_existing);
573612
CETL_DEBUG_ASSERT(shared_client != nullptr, "");
613+
614+
// This client impl node might be in the list of previously unreferenced nodes -
615+
// the ones that are going to be deleted asynchronously (by the `destroyUnreferencedNodes`).
616+
// If it's the case, we need to remove it from the list b/c it's going to be referenced.
617+
shared_client->unlinkIfReferenced();
618+
574619
return shared_client;
575620
}
576621

@@ -628,30 +673,57 @@ class Presentation final : private detail::IPresentationDelegate
628673
}
629674

630675
template <typename SharedNode>
631-
void releaseSharedNode(SharedNode* const shared_node, cavl::Tree<SharedNode>& tree) const noexcept
676+
static void forgetSharedNode(SharedNode& shared_node) noexcept
632677
{
633-
CETL_DEBUG_ASSERT(shared_node != nullptr, "");
678+
CETL_DEBUG_ASSERT(shared_node.isLinked(), "");
679+
CETL_DEBUG_ASSERT(!shared_node.isReferenced(), "");
634680

635-
// TODO: make it async (deferred to "on idle" callback).
636-
tree.remove(shared_node);
637-
shared_node->destroy();
681+
// Remove the node from its tree (if it still there),
682+
// as well as from the list of unreferenced nodes (b/c we gonna finally destroy it).
683+
shared_node.remove(); // from the tree
684+
shared_node.unlinkIfReferenced(); // from the list
685+
}
686+
687+
void destroyUnreferencedNodes() const noexcept
688+
{
689+
// In the loop, destruction of a shared object also removes it from the list of unreferenced nodes.
690+
// So, it implicitly updates the `unreferenced_nodes_` list.
691+
while (unreferenced_nodes_.next_node != &unreferenced_nodes_)
692+
{
693+
auto* const shared_obj = static_cast<detail::SharedObject*>(unreferenced_nodes_.next_node);
694+
shared_obj->destroy();
695+
}
638696
}
639697

640698
// MARK: IPresentationDelegate
641699

642-
void releaseSharedClient(detail::SharedClient* const shared_client) noexcept override
700+
void markSharedObjAsUnreferenced(detail::SharedObject& shared_obj) noexcept override
701+
{
702+
// We are not going to destroy the shared object immediately, but schedule it for deletion.
703+
// This is b/c destruction of shared objects may be time-consuming (f.e. closing under the hood sockets).
704+
// Double-linked list is used to avoid the need to traverse the tree of shared objects.
705+
//
706+
CETL_DEBUG_ASSERT(!shared_obj.isReferenced(), "");
707+
shared_obj.linkAsUnreferenced(unreferenced_nodes_);
708+
//
709+
const auto result = unref_nodes_deleter_callback_.schedule(Schedule::Once{executor_.now()});
710+
CETL_DEBUG_ASSERT(result, "Should not fail b/c we never reset `unref_nodes_deleter_callback_`.");
711+
(void) result;
712+
}
713+
714+
void forgetSharedClient(detail::SharedClient& shared_client) noexcept override
643715
{
644-
releaseSharedNode(shared_client, shared_client_nodes_);
716+
forgetSharedNode(shared_client);
645717
}
646718

647-
void releasePublisherImpl(detail::PublisherImpl* const publisher_impl) noexcept override
719+
void forgetPublisherImpl(detail::PublisherImpl& publisher_impl) noexcept override
648720
{
649-
releaseSharedNode(publisher_impl, publisher_impl_nodes_);
721+
forgetSharedNode(publisher_impl);
650722
}
651723

652-
void releaseSubscriberImpl(detail::SubscriberImpl* const subscriber_impl) noexcept override
724+
void forgetSubscriberImpl(detail::SubscriberImpl& subscriber_impl) noexcept override
653725
{
654-
releaseSharedNode(subscriber_impl, subscriber_impl_nodes_);
726+
forgetSharedNode(subscriber_impl);
655727
}
656728

657729
// MARK: Data members:
@@ -662,6 +734,8 @@ class Presentation final : private detail::IPresentationDelegate
662734
cavl::Tree<detail::SharedClient> shared_client_nodes_;
663735
cavl::Tree<detail::PublisherImpl> publisher_impl_nodes_;
664736
cavl::Tree<detail::SubscriberImpl> subscriber_impl_nodes_;
737+
detail::UnRefNode unreferenced_nodes_;
738+
IExecutor::Callback::Any unref_nodes_deleter_callback_;
665739

666740
}; // Presentation
667741

include/libcyphal/presentation/presentation_delegate.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#ifndef LIBCYPHAL_PRESENTATION_DELEGATE_HPP_INCLUDED
77
#define LIBCYPHAL_PRESENTATION_DELEGATE_HPP_INCLUDED
88

9+
#include "shared_object.hpp"
10+
911
#include <cetl/pf17/cetlpf.hpp>
1012

1113
#include <type_traits>
@@ -84,9 +86,10 @@ class IPresentationDelegate
8486

8587
virtual cetl::pmr::memory_resource& memory() const noexcept = 0;
8688

87-
virtual void releaseSharedClient(SharedClient* shared_client) noexcept = 0;
88-
virtual void releasePublisherImpl(PublisherImpl* publisher_impl) noexcept = 0;
89-
virtual void releaseSubscriberImpl(SubscriberImpl* subscriber_impl) noexcept = 0;
89+
virtual void markSharedObjAsUnreferenced(SharedObject& shared_obj) noexcept = 0;
90+
virtual void forgetSharedClient(SharedClient& shared_client) noexcept = 0;
91+
virtual void forgetPublisherImpl(PublisherImpl& publisher_impl) noexcept = 0;
92+
virtual void forgetSubscriberImpl(SubscriberImpl& subscriber_impl) noexcept = 0;
9093

9194
protected:
9295
IPresentationDelegate() = default;

include/libcyphal/presentation/publisher_impl.hpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ namespace detail
3636
class PublisherImpl final : public cavl::Node<PublisherImpl>, public SharedObject
3737
{
3838
public:
39+
using Node::remove;
40+
using Node::isLinked;
41+
3942
PublisherImpl(IPresentationDelegate& delegate, UniquePtr<transport::IMessageTxSession> msg_tx_session)
4043
: delegate_{delegate}
4144
, msg_tx_session_{std::move(msg_tx_session)}
@@ -70,19 +73,22 @@ class PublisherImpl final : public cavl::Node<PublisherImpl>, public SharedObjec
7073
{
7174
if (SharedObject::release())
7275
{
73-
delegate_.releasePublisherImpl(this);
76+
delegate_.markSharedObjAsUnreferenced(*this);
7477
return true;
7578
}
7679

7780
return false;
7881
}
7982

83+
private:
84+
// MARK: SharedObject
85+
8086
void destroy() noexcept override
8187
{
88+
delegate_.forgetPublisherImpl(*this);
8289
destroyWithPmr(this, delegate_.memory());
8390
}
8491

85-
private:
8692
// MARK: Data members:
8793

8894
IPresentationDelegate& delegate_;

include/libcyphal/presentation/shared_object.hpp

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,49 @@ namespace presentation
2525
namespace detail
2626
{
2727

28+
/// @brief Defines double-linked list of unreferenced nodes.
29+
///
30+
struct UnRefNode
31+
{
32+
void linkAsUnreferenced(UnRefNode& origin)
33+
{
34+
CETL_DEBUG_ASSERT((prev_node != nullptr) == (next_node != nullptr), "Should be both or none.");
35+
36+
// Already linked?
37+
if ((nullptr == prev_node) && (nullptr == next_node))
38+
{
39+
// Link to the end of the list, so that the object is destroyed in the order of unreferencing.
40+
//
41+
next_node = &origin;
42+
prev_node = origin.prev_node;
43+
origin.prev_node->next_node = this;
44+
origin.prev_node = this;
45+
}
46+
}
47+
48+
void unlinkIfReferenced()
49+
{
50+
CETL_DEBUG_ASSERT((prev_node != nullptr) == (next_node != nullptr), "Should be both or none.");
51+
52+
// Already unlinked?
53+
if ((nullptr != prev_node) && (nullptr != next_node))
54+
{
55+
prev_node->next_node = next_node;
56+
next_node->prev_node = prev_node;
57+
58+
next_node = nullptr;
59+
prev_node = nullptr;
60+
}
61+
}
62+
63+
UnRefNode* prev_node{nullptr};
64+
UnRefNode* next_node{nullptr};
65+
66+
}; // UnRefNode
67+
2868
/// @brief Defines the base class for all classes that need to be shared (using reference count).
2969
///
30-
class SharedObject
70+
class SharedObject : public UnRefNode
3171
{
3272
public:
3373
SharedObject() = default;
@@ -38,6 +78,13 @@ class SharedObject
3878
SharedObject& operator=(const SharedObject&) = delete;
3979
SharedObject& operator=(SharedObject&&) noexcept = delete;
4080

81+
/// @brief Gets boolean indicating whether the object is referenced at least once.
82+
///
83+
bool isReferenced() const noexcept
84+
{
85+
return ref_count_ > 0;
86+
}
87+
4188
/// @brief Increments the reference count.
4289
///
4390
void retain() noexcept

0 commit comments

Comments
 (0)