1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_THREAD_POOL_TRACKED_REF_H_
#define BASE_TASK_THREAD_POOL_TRACKED_REF_H_
#include <optional>
#include "base/atomic_ref_count.h"
#include "base/check.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/synchronization/waitable_event.h"
namespace base {
namespace internal {
// TrackedRefs are effectively a ref-counting scheme for objects that have a
// single owner.
//
// Deletion is still controlled by the single owner but ~T() itself will block
// until all the TrackedRefs handed by its TrackedRefFactory have been released
// (by ~TrackedRef<T>()).
//
// Just like WeakPtrFactory: TrackedRefFactory<T> should be the last member of T
// to ensure ~TrackedRefFactory<T>() runs first in ~T().
//
// The owner of a T should hence be certain that the last TrackedRefs to T are
// already gone or on their way out before destroying it or ~T() will hang
// (indicating a bug in the tear down logic -- proper refcounting on the other
// hand would result in a leak).
//
// TrackedRefFactory only makes sense to use on types that are always leaked in
// production but need to be torn down in tests (blocking destruction is
// impractical in production).
//
// Why would we ever need such a thing? In thread_pool there is a clear
// ownership hierarchy with mostly single owners and little refcounting. In
// production nothing is ever torn down so this isn't a problem. In tests
// however we must JoinForTesting(). At that point, all the raw back T* refs
// used by the worker threads are problematic because they can result in use-
// after-frees if a worker outlives the deletion of its corresponding
// ThreadPool/TaskTracker/ThreadGroup/etc.
//
// JoinForTesting() isn't so hard when all workers are managed. But with cleanup
// semantics (reclaiming a worker who's been idle for too long) it becomes
// tricky because workers can go unaccounted for before they exit their main
// (https://crbug.com/827615).
//
// For that reason and to clearly document the ownership model, thread_pool
// uses TrackedRefs.
//
// On top of being a clearer ownership model than proper refcounting, a hang in
// tear down in a test with out-of-order tear down logic is much preferred to
// letting its worker thread and associated constructs outlive the test
// (potentially resulting in flakes in unrelated tests running later in the same
// process).
//
// Note: While there's nothing thread_pool specific about TrackedRefs it
// requires an ownership model where all the TrackedRefs are released on other
// threads in sync with ~T(). This isn't a typical use case beyond shutting down
// ThreadPool in tests and as such this is kept internal here for now.
template <class T>
class TrackedRefFactory;
// TrackedRef<T> can be used like a T*.
template <class T>
class TrackedRef {
public:
// Moveable and copyable.
TrackedRef(TrackedRef<T>&& other)
: ptr_(other.ptr_), factory_(other.factory_) {
// Null out |other_|'s factory so its destructor doesn't decrement
// |live_tracked_refs_|.
other.factory_ = nullptr;
}
TrackedRef(const TrackedRef<T>& other)
: ptr_(other.ptr_), factory_(other.factory_) {
factory_->live_tracked_refs_.Increment();
}
// Intentionally not assignable for now because it makes the logic slightly
// convoluted and it's not a use case that makes sense for the types using
// this at the moment.
TrackedRef& operator=(TrackedRef<T>&& other) = delete;
TrackedRef& operator=(const TrackedRef<T>& other) = delete;
~TrackedRef() {
if (factory_ && !factory_->live_tracked_refs_.Decrement()) {
DCHECK(factory_->ready_to_destroy_);
DCHECK(!factory_->ready_to_destroy_->IsSignaled());
factory_->ready_to_destroy_->Signal();
}
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; }
bool operator==(const void* compared_ptr) const {
return ptr_ == compared_ptr;
}
// Returns the raw pointer stored in this TrackedRef. This is occasionally
// useful for operations in scope but, as with other smart pointers, it
// shouldn't be used beyond the scope of this TrackedRef.
T* get() const { return ptr_; }
private:
friend class TrackedRefFactory<T>;
TrackedRef(T* ptr, TrackedRefFactory<T>* factory)
: ptr_(ptr), factory_(factory) {
factory_->live_tracked_refs_.Increment();
}
raw_ptr<T, LeakedDanglingUntriaged> ptr_;
raw_ptr<TrackedRefFactory<T>, LeakedDanglingUntriaged> factory_;
};
// TrackedRefFactory<T> should be the last member of T.
template <class T>
class TrackedRefFactory {
public:
explicit TrackedRefFactory(T* ptr)
: ptr_(ptr), self_ref_(TrackedRef<T>(ptr_.get(), this)) {
DCHECK(ptr_);
}
TrackedRefFactory(const TrackedRefFactory&) = delete;
TrackedRefFactory& operator=(const TrackedRefFactory&) = delete;
~TrackedRefFactory() {
// Enter the destruction phase.
ready_to_destroy_.emplace();
// Release self-ref. If this was the last one it will signal the event right
// away. Otherwise it establishes an happens-after relationship between
// |ready_to_destroy.emplace()| and the eventual
// |ready_to_destroy_->Signal()|.
self_ref_.reset();
ready_to_destroy_->Wait();
}
TrackedRef<T> GetTrackedRef() {
// TrackedRefs cannot be obtained after |live_tracked_refs_| has already
// reached zero. In other words, the owner of a TrackedRefFactory shouldn't
// vend new TrackedRefs while it's being destroyed (owners of TrackedRefs
// may still copy/move their refs around during the destruction phase).
DCHECK(!live_tracked_refs_.IsZero());
return TrackedRef<T>(ptr_.get(), this);
}
private:
friend class TrackedRef<T>;
FRIEND_TEST_ALL_PREFIXES(TrackedRefTest, CopyAndMoveSemantics);
const raw_ptr<T> ptr_;
// The number of live TrackedRefs vended by this factory.
AtomicRefCount live_tracked_refs_{0};
// Non-null during the destruction phase. Signaled once |live_tracked_refs_|
// reaches 0. Note: making this optional and only initializing it in the
// destruction phase avoids keeping a handle open for the entire session.
std::optional<WaitableEvent> ready_to_destroy_;
// TrackedRefFactory holds a TrackedRef as well to prevent
// |live_tracked_refs_| from ever reaching zero before ~TrackedRefFactory().
std::optional<TrackedRef<T>> self_ref_;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_THREAD_POOL_TRACKED_REF_H_
|