-
Notifications
You must be signed in to change notification settings - Fork 1
/
ObjectTracker.h
335 lines (293 loc) · 13.4 KB
/
ObjectTracker.h
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#pragma once
#include "threadsafe.h"
#include "AIReadWriteSpinLock.h"
#include "utils/Badge.h"
#include "utils/is_complete.h"
#include <type_traits>
#include <utility>
#include <memory>
#include <atomic>
#include "debug.h"
// Let ObjectTracker.inl.h know that it is needed.
#define THREADSAFE_OBJECT_TRACKER_H
// threadsafe::ObjectTracker
//
// Allows objects to have a non-moving tracker object, allocated on the heap,
// that manages a pointer that points back to them, all while being thread-safe.
//
// This allows other objects to point to the tracker object without having
// to worry if the tracked object is moved in memory.
//
// Usage:
#if -0 // EXAMPLE_CODE
// Forward declare the object that should be tracked and protected against concurrent access.
class locked_Node;
//-----------------------------------------------------------------------------
#if UNLOCKED_TYPE_IS_TYPEDEF
// 1) If Node is a typedef, then it has to be defined next:
// Define the Unlocked version (using the desired locking policy):
using Node = threadsafe::UnlockedTrackedObject<locked_Node, threadsafe::policy::ReadWrite<AIReadWriteMutex>>;
#else
// 2) otherwise, forward declare Node.
class Node;
#endif
// Then define a corresponding tracker class.
#if TRACKER_IS_TYPEDEF
// Either also as a typedef:
using NodeTracker = threadsafe::ObjectTracker<Node, locked_Node, threadsafe::policy::ReadWrite<AIReadWriteMutex>>;
#else
// Or derived from ObjectTracker:
//
// Note: if you run into the compile error 'incomplete locked_Node' or UnlockedTrackedObject<locked_Node, ...> during the
// instantiation of the destructor of this NodeTracker class, then move its constructors and destructor out of the header
// (to a .cxx file).
class NodeTracker : public threadsafe::ObjectTracker<Node, locked_Node, threadsafe::policy::ReadWrite<AIReadWriteMutex>>
{
public:
// The arguments must be a Badge and a Node const& which is passed to ObjectTracker base class.
NodeTracker(utils::Badge<threadsafe::TrackedObject<Node, NodeTracker>>, Node const& tracked) :
threadsafe::ObjectTracker<Node, locked_Node, threadsafe::policy::ReadWrite<AIReadWriteMutex>>(tracked) { }
};
#endif
// Finally define the locked type (the type that can only be accessed through an
// access object, like NodeTracker::rat or NodeTracker::wat).
//
// The to-be-tracked object must be derived from threadsafe::TrackedObject,
// passing both, the Unlocked type as well as the tracker class.
class locked_Node : public threadsafe::TrackedObject<Node, NodeTracker>
{
private:
std::string s_;
public:
Node(std::string const& s) : s_(s) { }
std::string const& s() const { return s_; }
};
#if !UNLOCKED_TYPE_IS_TYPEDEF
// If Node is not a typedef (2) then define Node after locked_Node:
class Node : public threadsafe::UnlockedTrackedObject<locked_Node, threadsafe::policy::ReadWrite<AIReadWriteMutex>>
{
public:
using threadsafe::ObjectTracker<Node, locked_Node, threadsafe::policy::Primitive<std::mutex>>::ObjectTracker;
};
#endif
// Include this in every .cxx that uses ObjectTracker.h somehow!
#include "ObjectTracker.inl.h"
int main()
{
// Now one can construct a Node object:
Node node("hello");
// And obtain the tracker at any moment (also after it was moved):
std::weak_ptr<NodeTracker> node_tracker = node;
// And then even if node is moved,
Node node2(std::move(node));
// node_tracker will point to the correct instance (node2 in this case):
auto node_r{node_tracker.lock()->tracked_rat()};
std::cout << "s = " << node_r->s() << std::endl; // Prints "s = hello".
}
#endif // EXAMPLE_CODE
namespace threadsafe {
// ObjectTracker
//
// The type of the tracker returned by the above class (UnlockedTrackedObject).
// It is based around an UnlockedBase that provides both, the data pointer
// and the data mutex pointer and is itself protected by Unlocked.
//
// The template parameter TrackedType must be the above UnlockedTrackedObject.
// This class then must be passed as second template parameter to TrackedObject,
// see below.
//
// Note that it is allowed to derive from ObjectTracker, but a typical usage
// will be to use it as-is for your tracker type.
//
template<typename TrackedType, typename TrackedLockedType, typename POLICY_MUTEX>
class ObjectTracker
{
public:
using tracked_type = TrackedType;
using tracked_locked_type = TrackedLockedType;
using policy_type = POLICY_MUTEX;
using unlocked_type = UnlockedBase<tracked_locked_type, policy_type>;
using crat = typename unlocked_type::crat;
using rat = typename unlocked_type::rat;
using wat = typename unlocked_type::wat;
using w2rCarry = typename unlocked_type::w2rCarry;
private:
// This class is used to get access to the protected m_base.
class UnlockedBaseTrackedObject : public UnlockedBase<tracked_locked_type, policy_type>
{
public:
using UnlockedBase<tracked_locked_type, policy_type>::UnlockedBase;
void set_tracked_unlocked(tracked_locked_type* base)
{
this->m_base = base;
}
void update_mutex_pointer(auto* mutex_ptr)
{
if constexpr (std::is_same_v<wat, WriteAccess<Unlocked<tracked_type, policy_type>>>)
this->m_read_write_mutex_ptr = mutex_ptr;
else if constexpr (std::is_same_v<wat, Access<Unlocked<tracked_type, policy_type>>>)
this->m_primitive_mutex_ptr = mutex_ptr;
}
};
protected:
using tracked_unlocked_ptr_type = Unlocked<UnlockedBaseTrackedObject, policy::ReadWrite<AIReadWriteSpinLock>>;
tracked_unlocked_ptr_type tracked_unlocked_ptr_;
// Used by trackers that are derived from ObjectTracker.
ObjectTracker(tracked_type& tracked_unlocked) : tracked_unlocked_ptr_(tracked_unlocked) { }
public:
// Construct a new ObjectTracker that tracks tracked_unlocked.
template<typename TrackerType>
ObjectTracker(utils::Badge<TrackedObject<tracked_type, TrackerType>>, tracked_type const& tracked_unlocked) :
tracked_unlocked_ptr_(tracked_unlocked) { }
// This is called when the object is moved in memory, see below.
template<typename TrackerType>
void set_tracked_unlocked(utils::Badge<TrackedObject<tracked_type, TrackerType>>, tracked_type* tracked_unlocked_ptr)
{
// This function should not be called while the tracker is being constructed!
ASSERT(debug_initialized_);
typename tracked_unlocked_ptr_type::wat tracked_unlocked_ptr_w(tracked_unlocked_ptr_);
// This is called while the mutex of the tracked_type is locked.
tracked_unlocked_ptr_w->set_tracked_unlocked(tracked_unlocked_ptr);
}
void update_mutex_pointer(auto* mutex_ptr)
{
// This function should not be called while the tracker is being constructed!
ASSERT(debug_initialized_);
typename tracked_unlocked_ptr_type::wat tracked_unlocked_ptr_w(tracked_unlocked_ptr_);
tracked_unlocked_ptr_w->update_mutex_pointer(mutex_ptr);
}
// Accessors.
rat tracked_rat()
{
// This function should not be called while the tracker is being constructed!
ASSERT(debug_initialized_);
typename tracked_unlocked_ptr_type::rat tracked_unlocked_ptr_r(tracked_unlocked_ptr_);
// rat wants to be explicitly constructed from a non-const reference.
return rat{const_cast<UnlockedBaseTrackedObject&>(*tracked_unlocked_ptr_r)};
}
wat tracked_wat()
{
// This function should not be called while the tracker is being constructed!
ASSERT(debug_initialized_);
typename tracked_unlocked_ptr_type::rat tracked_unlocked_ptr_r(tracked_unlocked_ptr_);
return wat{const_cast<UnlockedBaseTrackedObject&>(*tracked_unlocked_ptr_r)};
}
#if CW_DEBUG
private:
bool debug_initialized_ = false;
public:
void set_is_initialized() { debug_initialized_ = true; }
#endif
};
// TrackedObject
//
// Base class of the unlocked data type.
// The template parameters must be (derived from) UnlockedTrackedObject and ObjectTracker respectively.
//
template<typename TrackedType, typename Tracker>
class TrackedObject
{
public:
using tracked_type = TrackedType;
using tracker_type = Tracker;
protected:
std::shared_ptr<Tracker> tracker_;
TrackedObject();
TrackedObject(TrackedObject&& other);
~TrackedObject();
public:
// Accessor for the Tracker object. Make sure to keep the TrackedObject alive while using this.
Tracker const& tracker() const
{
// Note that tracker_ can only be null when the Tracker was moved.
// Do not call this function (or any other member function except the destructor) on a moved object!
ASSERT(tracker_);
return *tracker_;
}
Tracker& tracker()
{
// See above.
ASSERT(tracker_);
return *tracker_;
}
// Automatic conversion to a weak_ptr.
operator std::weak_ptr<Tracker>() const { return tracker_; }
};
#ifdef CWDEBUG
template<typename TrackedLockedType, typename POLICY_MUTEX>
class SanityCheckArgsOfUnlockedTrackedObject
{
static_assert(utils::is_complete_v<TrackedLockedType>,
"The first template parameter of UnlockedTrackedObject must already be complete, but isn't.");
static_assert(utils::is_complete_v<POLICY_MUTEX>,
"The second template parameter of UnlockedTrackedObject must already be complete, but isn't.");
// The following also fails if TrackedLockedType has two TrackedObject base classes, which is not allowed.
static_assert(utils::is_derived_from_specialization_of_v<TrackedLockedType, TrackedObject>,
"The first template parameter of UnlockedTrackedObject must be derived from a single threadsafe::TrackedObject<TrackedType, Tracker>, but isn't.");
};
#endif
// UnlockedTrackedObject
//
// The type of an unlocked tracked object: this type wraps TrackedLockedType
// protecting it against concurrent access using POLICY_MUTEX, just like Unlocked
// and UnlockedBase, but simultaneously supports tracking (TrackedLockedType
// must be derived from TrackedObject<...> which creates the tracker and
// makes a reference to the that tracker available through the `tracker()`
// member function).
//
// Note: both TrackedLockedType and POLICY_MUTEX must be complete at this point
// because the base class Unlocked<TrackedLockedType, POLICY_MUTEX> derives from
// both.
template<typename TrackedLockedType, typename POLICY_MUTEX>
class UnlockedTrackedObject :
#ifdef CWDEBUG
SanityCheckArgsOfUnlockedTrackedObject<TrackedLockedType, POLICY_MUTEX>,
#endif
public Unlocked<TrackedLockedType, POLICY_MUTEX>
{
public:
UnlockedTrackedObject(typename Unlocked<TrackedLockedType, POLICY_MUTEX>::crat const& unlocked_r) :
Unlocked<TrackedLockedType, POLICY_MUTEX>(unlocked_r) { }
// Provide all the normal constructors of Unlocked, except the move constructor which is overridden below.
template<typename... ARGS>
requires
(sizeof...(ARGS) == 0 ||
(!std::is_base_of_v<Unlocked<TrackedLockedType, POLICY_MUTEX>, std::decay_t<std::tuple_element_t<0, std::tuple<ARGS...>>>> &&
!std::is_convertible_v<std::decay_t<std::tuple_element_t<0, std::tuple<ARGS...>>>,
LockFinalCopy<Unlocked<TrackedLockedType, POLICY_MUTEX>>> &&
!std::is_convertible_v<std::decay_t<std::tuple_element_t<0, std::tuple<ARGS...>>>,
LockFinalMove<Unlocked<TrackedLockedType, POLICY_MUTEX>>>)) &&
(sizeof...(ARGS) != 1 || !std::is_convertible_v<std::tuple_element_t<0, std::tuple<ARGS...>>,
typename Unlocked<TrackedLockedType, POLICY_MUTEX>::crat const&>)
UnlockedTrackedObject(ARGS&&... args) :
Unlocked<TrackedLockedType, POLICY_MUTEX>(std::forward<ARGS>(args)...) { }
UnlockedTrackedObject(LockFinalCopy<UnlockedTrackedObject> orig) : Unlocked<TrackedLockedType, POLICY_MUTEX>(orig) { }
// Make sure that the normal copy constructor also uses the above.
UnlockedTrackedObject(UnlockedTrackedObject const& orig) : UnlockedTrackedObject(LockFinalCopy<UnlockedTrackedObject>{orig}) { }
// Move constructor with tracking support: if the final object is moved then first
// the mutex of other is write locked, then that object is "moved" (moving the underlaying
// data object, but creating a new policy mutex). This also updates the pointer of the
// tracker that points to that data object. Finally, in the body of the constructor,
// the tracker is updated to point to the newly created mutex. At the end of the constructor
// of the final object, other is unlocked.
template<typename... ARGS>
requires ((!std::is_base_of_v<Unlocked<TrackedLockedType, POLICY_MUTEX>, std::decay_t<ARGS>> &&
!std::is_convertible_v<std::decay_t<ARGS>, LockFinalCopy<Unlocked<TrackedLockedType, POLICY_MUTEX>>> &&
!std::is_convertible_v<std::decay_t<ARGS>, LockFinalMove<Unlocked<TrackedLockedType, POLICY_MUTEX>>>) && ...)
explicit UnlockedTrackedObject(LockFinalMove<UnlockedTrackedObject> other, ARGS&&... args) :
Unlocked<TrackedLockedType, POLICY_MUTEX>(std::move(other), std::forward<ARGS>(args)...)
{
auto& mutex = this->mutex();
this->tracker_->update_mutex_pointer(&mutex);
}
// Make sure that the normal move constructor also uses the above.
// Note that because the constructor that accepts a LockFinalCopy as first argument does not accept
// any other arguments, it is safe not to provide constructors that accept an rvalue reference plus
// additional arguments: that will just use the above constructor.
UnlockedTrackedObject(UnlockedTrackedObject&& other) : UnlockedTrackedObject(LockFinalMove<UnlockedTrackedObject>{std::move(other)}) { }
public:
// Give access to tracker.
using Unlocked<TrackedLockedType, POLICY_MUTEX>::tracker;
using Unlocked<TrackedLockedType, POLICY_MUTEX>::operator std::weak_ptr<typename TrackedLockedType::tracker_type>;
};
} // namespace threadsafe