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
base / task / thread_pool / tracked_ref.h [blame]
// 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_