template <typename TrackedType, typename HolderType, size_t user_tag_bits = 4u>
class ObjectTracker
Defined at line 143 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
ObjectTracker is a a kernel version of a multithreaded map container + allocator
specialized for uint32
<
--> object. It provides the standard map services
add, remove, get
&
enumerate but also the storage services of a pool allocator.
ObjectTracker fills the gap between the existing helpers:
- allocators cannot enumerate their live objects, the current solution is
to use a intrusive linked list to enumerate them which comes with 16 bytes
overhead and lookups are O(n).
- The WAVL tree supports fast lookups and iteration but each object must
pay 48+ bytes overhead, so it is only practical if the object weights upwards of
200 bytes.
The API is move-oriented, the typical lifecycle goes:
1 - Create object |o| to be tracked
2 - Move |o| into the tracker via add(o) which returns its |id|.
3 - Retrieve the object reference (via the holder) from get(id)
4 - call |o| methods via the holder
5 - Drop the holder
6 - Either remove the object from tracking via remove(id) or
goto step 3.
Using it
ObjectTracker takes two class template parameters:
A) TrackedType: the class to be stored and tracked, it must implement three
methods:
1- zx_status on_holder(uint32_t user_tag). Called when a Holder needs to
be created. If this method returns an error, the holder will not be
created and the error will be returned instead.
2- uint32_t on_tracked(). Called once the object is being tracked. The
returned value is the "user tag" included in the tracking id. See remarks
below.
3- zx_status_t on_remove(uint32_t user_tag). Called before is removed from
tracking. If this method returns an error, the object will not be removed
from the container and the error will be returned instead.
If the user_tag feature is not needed, on_tracked() and on_remove() methods can
have a no-op implementations as follows:
uint32_t on_tracked() {
return 0u;
}
zx_status_t on_remove(uint32_t) {
return ZX_OK;
}
All the above callbacks are issued with a spinlock acquired that protects the page
where the tracked object lives. Therefore, no calls to the tracker should be issued
from the callback or calls that acquire regular mutexes. Calls that acquire
spinlocks are ok.
B) HolderType: an object of this class is returned when ObjectTracker::get()
is successful. The Holder is created via the HolderType(TrackedType
&
) or the
HolderType(const TrackedType
&
) ctor. If HolderType keeps a weak reference to the
TrackedType then it must collaborate with it to prevent ObjectTracker::remove()
from succeeding by returning an error from TrackedType::on_remove().
The TrackedType has one more requirement: it must have a boring destructor
for the particular case when it is called for a moved-from state. In other words
assume a TrackedType = TFoo, then
TFoo destination;
{
// stack allocated storage for |source|.
alignas(Foo) char data[sizeof(Foo)];
TFoo* source = new (data) TFoo();
......
......
destination = std::move(*source);
avoid_dtor = true;
if (!avoid_dtor) {
source->~TFoo();
}
}
The program above is correct for the TFoo type. Note that if TFoo is not in the
moved-from state, the destructor can do all sorts of critical operations, this means
the object tracker that fail the ktl::is_trivially_destructible
<TrackedType
> test.
Using the user_tag
A user tag is a small value, cookie-like, with as many bits as the template parameter
user_tag_bits. It is provided by the tracked object during ObjectTracker::add() via the
callback on_tracked() and returned as mixed in the returned id. This tag is given back
to the tracked object during the ObjectTracker::get() in the on_holder()
callback or during ObjectTracker::remove() in the on_remove() callback.
The tracked object has full freedom how to interpret the tag, it might require to be
provided back the same tag or a different value. The ObjectTracker::encode() and
decode() static functions can be used by clients to extract or replace the tag returned
by the tracked object, except in one case, during the ObjectTracker destruction
or if triggered manually via destroy_all() the on_remove() callback is issued with
the special value kTagDestroy, which is hardcoded to be the highest value possible
for an unsigned number of size user_tag_bits.
Public Members
static const uint32_t kTagDestroy
Public Methods
void ObjectTracker<TrackedType, HolderType, user_tag_bits> ()
Defined at line 145 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
void ObjectTracker<TrackedType, HolderType, user_tag_bits> (const ObjectTracker<TrackedType, HolderType, user_tag_bits> & )
Defined at line 146 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
void ObjectTracker<TrackedType, HolderType, user_tag_bits> (ObjectTracker<TrackedType, HolderType, user_tag_bits> && )
Defined at line 147 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
void ObjectTracker<TrackedType, HolderType, user_tag_bits> (page_cache::PageCache * page_cache)
Defined at line 149 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
void ~ObjectTracker<TrackedType, HolderType, user_tag_bits> ()
Defined at line 150 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
uint32_t obs_per_page ()
How many objects fit in a page. Only interesting if you are also using
encode() or decode(), or deeply care about efficient storage.
Defined at line 228 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
uint32_t max_pages ()
Ids are uint32_t and must fit the index, tag and, page id which limits
the number of pages.
Defined at line 232 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
zx::result<HolderType> get (uint32_t id)
Make a holder from a tracked object pointed by |id|. The only error possible is
ZX_ERR_NOT_FOUND. Otherwise an HolderType is returned.
Defined at line 564 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
zx_status_t get (ktl::span<const uint32_t> ids, ktl::span<HolderType> holders)
Batch-get. Same considerations as the single-issue get apply.
Defined at line 578 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
zx::result<uint32_t> add (TrackedType object)
Move the |object| into the tracker. The only error results are the ones returned
by PageCache, which are memory related: ZX_ERR_OUT_OF_MEMORY or ZX_ERR_OUT_OF_RANGE
if we have maxed out the number of pages that can be allocated. Otherwise the result is
the id of the object.
Because of the locking scheme, It is possible for another thread guessing ids to issue a
successful get() and operate on the |object| before this method returns. In other words
the object moved (or copied) from |object| can start receiving method calls before this
method returns but after it has been fully created.
Defined at line 599 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
zx_status_t add (ktl::span<TrackedType> objects, ktl::span<uint32_t> ids)
Move the |objects| into the tracker returning their assigned ids in |ids|.
The possible errors are the same as the single item Add(). In the case of an error
some elements might have been inserted already. If the user wants to recover from this
it is recommended that the output |ids| vector should contain zeros so if desired the
newly inserted objects might be removed.
Defined at line 630 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
zx::result<TrackedType> remove (uint32_t id)
Remove the (previously added) object from tracking. There are two
main error conditions:
- ZX_ERR_NOT_FOUND: the |id| does not correspond to an tracked object.
- The object does not want to be removed, that is, on_remove() returned an
error which is returned here. It might be that an alive HolderType object
is preventing the removal.
Defined at line 706 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
zx_status_t remove (ktl::span<const uint32_t> ids, ktl::span<TrackedType> objects)
Remove the previously added objects in |ids| and returns them in |objects|.
The input array is mutated.
In the case of failure, for example one of the ids is invalid, the rest are still
processed. This means that there are two outcomes:
- success: all objects are in the output vector
- failure: all objects that could be removed are destroyed.
Defined at line 730 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
size_t destroy_all ()
Destroy all the tracked objects.
This method is to be called when by construction you know that there are no outstanding
HolderType objects. It will remove (or assert if it can't) all the tracked objects and
call the destructors to each one.
Also beware that it will hold internal spinlocks so you want to make sure other threads
are not going to be adding or removing objects to the tracker concurrently.
Defined at line 751 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
size_t count_slow ()
Returns how many objects are being tracked. Only use this method
for testing.
Defined at line 776 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
size_t page_count ()
Returns how many pages are being used. You can use this method for
testing or for in-the-field memory diagnostics.
Defined at line 786 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
auto decode (uint32_t id)
Decodes an |id| returned by add() so the client can store the
id differently. For example if the number of objects is limited
by other means, an id can be compressed into 16 bits.
Defined at line 976 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
uint32_t encode (uint32_t page_id, uint32_t index, uint32_t user_tag)
Encodes the tuple returned by decode() back into a tracker id. Only
needed if you use decode() and store the id in an interesting way.
Defined at line 990 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h
uint32_t encode (uint32_t page_id, uint32_t index_and_tag)
Like encode but for a pre-combined index plus tag.
Defined at line 998 of file ../../zircon/kernel/lib/fbl/include/fbl/object_tracker.h