Task #3164
closedReduce usage of shared_ptr in FIB/PIT/StrategyChoice/Measurements
100%
Description
In NameTree-attached tables (including FIB, PIT, StrategyChoice, Measurements) API, change shared_ptr
parameters to constant or mutable reference parameters, wherever it's feasible.
If necessary, Entry types can be declared as enable_shared_from_this
.
Updated by Junxiao Shi about 10 years ago
- Related to Task #3205: Reduce usage of shared_ptr in forwarding added
Updated by Junxiao Shi almost 10 years ago
- Related to Feature #3361: FIB/PIT/CS/Measurements: erase by iterator added
Updated by Junxiao Shi almost 10 years ago
As stated in #3361 note-5, there's a design choice of whether to use iterators or references as handles of table items.
My thinking is:
- When the callee can inspect the item, use a const reference.
- When the callee can modify the item itself but cannot delete it, use a mutable reference.
- When the callee can delete the item, use an iterator.
Updated by Junxiao Shi almost 10 years ago
20160121 conference call agrees with the idea that iterators should be used in place of shared_ptr
s.
Updated by Junxiao Shi over 9 years ago
- Related to Task #3546: PIT entry: move away forwarding semantics added
Updated by Junxiao Shi over 9 years ago
- Status changed from New to In Progress
- Assignee set to Junxiao Shi
Updated by Junxiao Shi over 9 years ago
- % Done changed from 0 to 10
https://gerrit.named-data.net/2917 patchset1 shows my idea on how to change FIB APIs.
Compilation will fail because I haven't changed other parts of the codebase that uses FIB.
But I need some early feedback on the API changes.
Updated by Junxiao Shi over 9 years ago
- % Done changed from 10 to 20
https://gerrit.named-data.net/2917 patchset2 adapts the rest of codebase to new FIB APIs.
There's one issue:
Some strategies (access and ncc) need the FIB entry after afterReceiveInterest
or afterReceiveNack
returns, but they couldn't retain const fib::Entry&
reference because it's possible that management would delete the FIB entry, and the retained reference would be a dangling pointer.
An obvious workaround is to declare fib::Entry
as enable_shared_from_this
.
But I think it makes sense to add fib::Entry& Strategy::lookupFib(const pit::Entry&) const
, and eliminate fibEntry
parameter on afterReceiveInterest
and afterReceiveNack
triggers.
The benefits are:
- Strategy can always access the latest effective FIB entry.
- Strategy doesn't need to retain a FIB entry reference, saving a bit of memory.
- If a strategy trigger doesn't use the FIB entry, ContentStore miss pipeline or incoming Nack pipeline doesn't need to perform FIB lookup, eliminating an obvious waste.
Updated by Junxiao Shi over 9 years ago
I discussed with Zhuo about note-8 idea, and he agrees with this idea. I'll make the changes under #3205.
Updated by Junxiao Shi over 9 years ago
https://gerrit.named-data.net/2917 patchset3 adopts the new Strategy
API from #3205, and completes FIB portion of this issue.
I will not move onto other tables until this Change is merge.
Updated by Junxiao Shi over 9 years ago
- % Done changed from 20 to 30
https://gerrit.named-data.net/2924 changes name_tree::Entry
so that Strategy Choice entry is attached as unique_ptr
instead of shared_ptr
.
Another commit will change Strategy Choice entry itself to avoid shared_ptr<Strategy>
.
I decide to separate these two commits, so that each commit is smaller and easier to review.
I did not separate them in FIB due to the extensive usage of shared_ptr<fib::Entry>
elsewhere, which make separating commits harder than Strategy Choice.
Updated by Junxiao Shi over 9 years ago
- % Done changed from 30 to 40
https://gerrit.named-data.net/2935 changes StrategyChoice
table to store Strategy
instances with unique_ptr
instead of shared_ptr
.
Strategy Choice entry does not have any shared_ptr
so there's no change in there.
Updated by Junxiao Shi over 9 years ago
https://gerrit.named-data.net/2961 attaches unique_ptr<measurements::Entry>
onto name tree entry, and changes Measurements
MeasurementsAccessor
APIs to return Entry&
(non-nullable) or Entry*
(nullable).
In Measurements::extendLifetime
and Measurements::cleanup
, nte != nullptr
check is deleted, because in case the Entry&
passed as argument is a dangling reference, calling NameTree::lookup(entry)
is already undefined behavior.
This differs from the previous code in which a shared_ptr<measurements::Entry>
elsewhere could keep the Entry
alive after being detached from the name tree entry.
Updated by Junxiao Shi about 9 years ago
So far I have progressed through FIB, StrategyChoice, and Measurements. PIT is the last table left.
The plan is:
Change shared_ptr<Face> FaceRecord::m_face
to Face&
.
Having shared_ptr<Face>
not only is a small performance issue, but also causes the lifetime of Face
to be longer than necessary.
To avoid dangling reference, when a face is closed, the InRecord/OutRecord associated with this face should be deleted.
This would require a PIT enumeration which is expensive, but Fib::removeNextHopFromAllEntries
is already doing a NameTree::fullEnumerate, and FIB+PIT enumeration can be implemented as a single enumeration over the NameTree.
I'll do the PIT enumeration changes in #3685.
Change shared_ptr<Face>
parameters in PIT and PIT entry APIs to Face&
or const Face&
.
Keep shared_ptr<pit::Entry>
available.
weak_ptr<pit::Entry>
is widely used in strategies for timer callbacks. It's possible to eliminate those usages by introducing a single per-PIT-entry timer for strategy to use, but this would be a complicated change.
Therefore, PIT entry will continue be created and attached to name tree entry as shared_ptr<pit::Entry>
.
Pass pit::Entry
as const/mutable reference in forwarding.
This reduces the usage of shared_ptr<pit::Entry>
.
pit::Entry
should inherit from enable_shared_from_this
, so that shared_ptr<pit::Entry>
and weak_ptr<pit::Entry>
can be obtained from the reference when strategy needs it.
To simplify strategy code that only wants a weak_ptr<pit::Entry>
, C++17 enabled_shared_from_this<T>::weak_from_this()
should be backported into ndn-cxx/util/backports.hpp
.
Updated by Junxiao Shi about 9 years ago
- Related to Feature #3679: Decouple Forwarder and FaceTable added
Updated by Junxiao Shi about 9 years ago
- Related to Feature #3685: PIT: delete in-record and out-record when face is destroyed added
Updated by Davide Pesavento about 9 years ago
Junxiao Shi wrote:
Pass
pit::Entry
as const/mutable reference in forwarding.
This reduces the usage ofshared_ptr<pit::Entry>
.
pit::Entry
should inherit fromenable_shared_from_this
, so thatshared_ptr<pit::Entry>
andweak_ptr<pit::Entry>
can be obtained from the reference when strategy needs it.
hmm... is it worth changing at this point though? I'm inclined to say no. Just keep passing a shared_ptr
or weak_ptr
by const&
.
Updated by Junxiao Shi about 9 years ago
- Estimated time changed from 9.00 h to 15.00 h
Reply to note-17:
Yes it's worth changing. There are only sparse use cases where shared_ptr<pit::Entry>
and weak_ptr<pit::Entry>
are needed.
Updated by Davide Pesavento about 9 years ago
Junxiao Shi wrote:
Yes it's worth changing. There are only sparse use cases where
shared_ptr<pit::Entry>
andweak_ptr<pit::Entry>
are needed.
But there are no benefits in changing from const shared_ptr<pit::Entry>&
to pit::Entry&
, unless the shared_ptr
is removed completely and the ownership model changes from shared to exclusive, which is not happening for PIT entries. So, even if there was only one place where a shared/weak_ptr
was needed, it's still not worthwhile to make this change.
When the ownership model is shared, it's always better to be explicit and pass a shared_ptr
(possibly by const-ref) to a function that will (or may, in the const-ref case) keep a shared reference to the object. shared_from_this
is ugly and hackish, and should never be used except in very few specific cases (e.g. obtaining a shared_ptr
from naked this
, as the function name itself suggests). What you're proposing is an abuse of that functionality.
Updated by Junxiao Shi about 9 years ago
Reply to note-19:
Semantic-wise, the sole owner of a pit::Entry
is the name tree entry; strategy is allowed to retain weak_ptr<pit::Entry>
only.
How about an approach similar to ndn-cxx:commit:5bb314a7ea86e915e3fc32796ec2f3745e712983 Key
vs KeyImpl
?
void MyStrategy::oneFunction(pit::Entry& pitEntry)
{
pit::Handle hdl(pitEntry);
scheduler::schedule(time::seconds(1), [=] { this->anotherFunction(hdl); });
}
void MyStrategy::anotherFunction(pit::Handle hdl)
{
pit::Entry* pitEntry = hdl.get();
if (pitEntry == nullptr) {
// PIT entry is gone
return;
}
// use PIT entry
}
pit::Handle
internally keeps a weak_ptr<pit::Entry>
, but neither shared_ptr<pit::Entry>
nor weak_ptr<pit::Entry>
is directly exposed to the strategy.
Updated by Junxiao Shi about 9 years ago
- Related to Task #3687: Reduce usage of shared_ptr in NameTree added
Updated by Davide Pesavento about 9 years ago
Junxiao Shi wrote:
pit::Handle
internally keeps aweak_ptr<pit::Entry>
, but neithershared_ptr<pit::Entry>
norweak_ptr<pit::Entry>
is directly exposed to the strategy.
What's the benefit of not exposing shared/weak_ptr
to the strategy? What are you trying to accomplish? It seems to me that it would only obfuscate the code. The only advantage I see is that it would insulate the various strategies from further API breaks, but I don't think that's a goal for now (we don't have a stable strategy API yet).
Updated by Junxiao Shi about 9 years ago
The only advantage I see is that it would insulate the various strategies from further API breaks
It's the other way around. Strategy
API is designed to prevent mistakes in strategy implementations.
For example, a strategy does not have direct access to Measurements
table, but only filtered access through MeasurementsAccessor
.
Updated by Davide Pesavento about 9 years ago
Ok, but you still haven't explained why you don't want to directly expose a weak_ptr
to the strategies... is it to prevent strategies from calling lock()
on the weak_ptr
and keeping the object alive?
Updated by Junxiao Shi about 9 years ago
is it to prevent strategies from calling
lock()
on theweak_ptr
and keeping the object alive?
yes
Updated by Davide Pesavento about 9 years ago
Ok. Then we should make this Handle
class the only type through which strategies can manage/inspect PIT entries, i.e. we should never provide direct access to naked references or pointers, unlike your example in note-20, for memory safety reasons. All operations must be performed through a Handle
object, that internally lock()
s its private weak_ptr
and then forwards the call to the real Entry object. This seems to be exactly what pib::Key
does.
Unfortunately this approach doesn't scale very well (a "forwarding" method on the frontend object is necessary for every "real" method), and requires adding overloads that take a Handle
to all APIs invokable by strategies that currently take an Entry&
or Entry*
...
Updated by Junxiao Shi about 9 years ago
make this
Handle
class the only type through which strategies can manage/inspect PIT entries
Although it's an eventual goal to have proxy/restricted types on everything a strategy can get hold of, this is out of scope of this issue.
My only concern in this issue is the lifetime/ownership of pit::Entry
.
note-20 solves this concern and is a step toward the eventual goal.
Updated by Davide Pesavento about 9 years ago
If you want a temporary solution, then using weak_ptr
is a much safer and simpler (less code to maintain) approach. Just document that strategies are not allowed to call lock()
and keep the resulting shared_ptr
indefinitely.
Updated by Junxiao Shi about 9 years ago
Reply to note-28:
It's too easy for a strategy to make a mistake and retain shared_ptr<pit::Entry>
unless I prevent them from getting a shared_ptr altogether.
A disallowed usage of shared_from_this
is easily discoverable by glancing at the code, but a mistake of passing shared_ptr into bind or captured lambda isn't so obvious, and that's what note-20 is trying to avoid.
Updated by Davide Pesavento about 9 years ago
So you avoid the lesser evil (retaining shared_ptr
s to PIT entries), but you encourage using dangerous and unsafe naked pointers, and you don't disallow keeping naked references and/or pointers to objects that can be deallocated at any time...!
Updated by Junxiao Shi about 9 years ago
Reply to note-30:
Keeping naked reference is already disallowed, although I could only enforce that through Doxygen for now.
Updated by Junxiao Shi about 9 years ago
- % Done changed from 40 to 60
https://gerrit.named-data.net/2999 fulfills note-14 first part.
Updated by Junxiao Shi about 9 years ago
I want a confirmation: can I proceed with note-20 design?
If not, this issue is completed, and PIT entry will be kept as shared_ptr<pit::Entry>
everywhere.
Updated by Junxiao Shi about 9 years ago
Davide has threatened a CodeReview-1 if I introduce a pit::Handle
class, and suggests:
Just use a weak_ptr... or a typedef for a weak_ptr so that we can later change it more easily to that "Handle" type that I was talking about in note-26. And simply document that locking the weak_ptr and keeping the returned shared_ptr beyond the execution of the strategy callback function is prohibited. In the same way that keeping a naked ref/ptr is already disallowed "only through doxygen" like you said in note-31.
In the future we can design and implement a proper wrapper class that prevents all these issues at compile time.
This implies anything similar to those listed in #3691-5 would not be usable.
There would be no benefit in passing weak_ptr<pit::Entry>
to Strategy APIs because it always needs to call lock
.
Therefore, I'd just adds some asserts to ensure no shared_ptr
is retained, but pass the parameter as const shared_ptr<pit::Entry>&
.
Updated by Junxiao Shi about 9 years ago
- Related to Task #3738: Simplify FIB/StrategyChoice iterators with Boost.Range added
Updated by Junxiao Shi about 9 years ago
- % Done changed from 60 to 70
https://gerrit.named-data.net/3083 moves PIT iterator to a separate file.
https://gerrit.named-data.net/3074 changes dereferenced type of PIT iterator to const shared_ptr<pit::Entry>&
.
Updated by Junxiao Shi about 9 years ago
- % Done changed from 70 to 80
https://gerrit.named-data.net/3085 changes Pit::erase
to accept Entry*
.
A benefit of using Entry*
instead of const shared_ptr<Entry>&
is: we can erase a PIT entry found by enumeration, which is useful in simulations.
Updated by Junxiao Shi about 9 years ago
- Status changed from In Progress to Code review
- % Done changed from 80 to 100
https://gerrit.named-data.net/3092 changes Pit::findAndInsert
to return const shared_ptr<pit::Entry>&
, and Pit::deleteInOutRecords
to take Entry*
.
Pit::findAndInsert
and pit::DataMatchResult
still use shared_ptr<pit::Entry>
or its const reference because forwarding strategy needs it.
This is the final Change under this issue.
Updated by Junxiao Shi about 9 years ago
- Status changed from Code review to Closed