Task #3164
closedReduce usage of shared_ptr in FIB/PIT/StrategyChoice/Measurements
Added by Junxiao Shi about 9 years ago. Updated over 8 years ago.
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 9 years ago
- Related to Task #3205: Reduce usage of shared_ptr in forwarding added
Updated by Junxiao Shi almost 9 years ago
- Related to Feature #3361: FIB/PIT/CS/Measurements: erase by iterator added
Updated by Junxiao Shi almost 9 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 9 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 8 years ago
- Related to Task #3546: PIT entry: move away forwarding semantics added
Updated by Junxiao Shi over 8 years ago
- Status changed from New to In Progress
- Assignee set to Junxiao Shi
Updated by Junxiao Shi over 8 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 8 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 8 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 8 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 8 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 8 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 8 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 over 8 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 over 8 years ago
- Related to Feature #3679: Decouple Forwarder and FaceTable added
Updated by Junxiao Shi over 8 years ago
- Related to Feature #3685: PIT: delete in-record and out-record when face is destroyed added
Updated by Davide Pesavento over 8 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 over 8 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 over 8 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 over 8 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 over 8 years ago
- Related to Task #3687: Reduce usage of shared_ptr in NameTree added
Updated by Davide Pesavento over 8 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 over 8 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 over 8 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 over 8 years ago
is it to prevent strategies from calling
lock()
on theweak_ptr
and keeping the object alive?
yes
Updated by Davide Pesavento over 8 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 over 8 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 over 8 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 over 8 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 over 8 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 over 8 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 over 8 years ago
- % Done changed from 40 to 60
https://gerrit.named-data.net/2999 fulfills note-14 first part.
Updated by Junxiao Shi over 8 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 over 8 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 over 8 years ago
- Related to Task #3738: Simplify FIB/StrategyChoice iterators with Boost.Range added
Updated by Junxiao Shi over 8 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 over 8 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 over 8 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 over 8 years ago
- Status changed from Code review to Closed