UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
HazardPointer.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4#include "Containers/Array.h"
5#include "CoreMinimal.h"
8#include "HAL/PlatformTLS.h"
9#include "HAL/UnrealMemory.h"
11
12#include <atomic>
13
15
17{
22{
23 friend class ::FHazardPointerCollection;
24
25protected:
26 void* Pointer;
28
29 virtual void Delete()
30 {
31 check(false);
32 };
33
34public:
36 {
37 //deliberately stomp over the vtable pointer so that this FHazardDeleter becomes a THazardDeleter
38 FPlatformMemory::Memcpy(this, &Other, sizeof(FHazardDeleter)); //-V598
39 }
40
42 {
43 //deliberately stomp over the vtable pointer so that this FHazardDeleter becomes a THazardDeleter
44 FPlatformMemory::Memcpy(this, &Other, sizeof(FHazardDeleter)); //-V598
45 return *this;
46 }
47
48 bool operator== (const FHazardDeleter& Other) const
49 {
50 return Other.Pointer == Pointer;
51 }
52};
53}
58{
59 template<typename, bool>
60 friend class THazardPointer;
61
62 struct FTlsData
63 {
65 double TimeOfLastCollection = .0;
66
67 ~FTlsData()
68 {
69 for (HazardPointer_Impl::FHazardDeleter& Deleter : ReclamationList)
70 {
71 Deleter.Delete();
72 }
73 ReclamationList.Empty();
74 }
75 };
76
77 class alignas(PLATFORM_CACHE_LINE_SIZE * 2) FHazardRecord
78 {
79 friend class FHazardPointerCollection;
80
81 template<typename, bool>
82 friend class THazardPointer;
83 static constexpr uintptr_t FreeHazardEntry = ~uintptr_t(0);
84
85 std::atomic<uintptr_t> Hazard{ FreeHazardEntry };
86#ifdef _MSC_VER
87 // UE_DEPRECATED - this is a workaround for https://developercommunity.visualstudio.com/t/VS-2022-1790-Preview-10-__builtin_arr/10519788 and should be removed after
88 // after 17.9 is no longer supported.
89 public:
90 FHazardRecord() = default;
91 private:
92#else
93 FHazardRecord() = default;
94#endif
95
96 inline void* GetHazard() const
97 {
98 return reinterpret_cast<void*>(Hazard.load(std::memory_order_acquire));
99 }
100
101 //assign hazard pointer once acquired
102 [[nodiscard]] inline void* SetHazard(void* InHazard)
103 {
104 Hazard.store(reinterpret_cast<uintptr_t>(InHazard), std::memory_order_release);
105 std::atomic_thread_fence(std::memory_order_seq_cst);
106 return reinterpret_cast<void*>(Hazard.load(std::memory_order_acquire));
107 }
108
109 //this thread wants to re-use the slot but does not want to hold onto the pointer
110 inline void Retire()
111 {
112 Hazard.store(0, std::memory_order_release);
113 }
114
115 //good to reuse by another thread
116 inline void Release()
117 {
118 Hazard.store(FreeHazardEntry, std::memory_order_release);
119 }
120 };
121
122 template<typename D>
123 class THazardDeleter final : public HazardPointer_Impl::FHazardDeleter
124 {
125 public:
127 {
128 static_assert(sizeof(THazardDeleter<D>) == sizeof(FHazardDeleter), "Size mismatch: we want to store and THazardDeleter in an FHazardDeleter array");
129 }
130
131 void Delete() override
132 {
133 D* Ptr = reinterpret_cast<D*>(Pointer);
134 delete Ptr;
135 }
136 };
137
138 static constexpr uint32 HazardChunkSize = 32;
139 struct FHazardRecordChunk
140 {
141 FHazardRecord Records[HazardChunkSize] = {};
142 std::atomic<FHazardRecordChunk*> Next{ nullptr };
143
144 inline void* operator new(size_t Size)
145 {
146 return FMemory::Malloc(Size, 128u);
147 }
148
149 inline void operator delete(void* Ptr)
150 {
151 FMemory::Free(Ptr);
152 }
153 };
154
155 FHazardRecordChunk Head;
156
157 FCriticalSection AllTlsVariablesCS;
158 FCriticalSection HazardRecordBlocksCS;
159 TArray<FTlsData*> AllTlsVariables;
160 TArray<FHazardRecordChunk*> HazardRecordBlocks;
161
162 uint32 CollectablesTlsSlot = FPlatformTLS::InvalidTlsSlot;
163 std::atomic_uint TotalNumHazardRecords{ HazardChunkSize };
164
166
167 //mark pointer for deletion
169
170 template<bool Cached>
171 CORE_API FHazardRecord* Grow();
172
173public:
175 {
176 CollectablesTlsSlot = FPlatformTLS::AllocTlsSlot();
177 }
178
180
181 //grab a hazard pointer and once hazard is set the other threads leave it alone
182 template<bool Cached>
183 inline FHazardRecord* Acquire()
184 {
185 struct FPseudo
186 {
187 static inline uint32 GetThreadId()
188 {
189 static std::atomic_uint counter{0};
190 uint32 value = counter.fetch_add(1, std::memory_order_relaxed);
191 value = ((value >> 16) ^ value) * 0x45d9f3b;
192 value = ((value >> 16) ^ value) * 0x45d9f3b;
193 value = (value >> 16) ^ value;
194 return value;
195 }
196 };
197
198 static thread_local uint32 StartIndex = FPseudo::GetThreadId();
199
200 FHazardRecordChunk* p = &Head;
201 if (Cached)
202 {
203 p = p->Next.load(std::memory_order_relaxed);
204 goto TestCondition;
205 }
206
207 //search HazardPointerList for an empty Entry
208 do
209 {
210 for (uint64 idx = 0; idx < HazardChunkSize; idx++)
211 {
212 uintptr_t Nullptr = 0;
213 uintptr_t FreeEntry = FHazardRecord::FreeHazardEntry;
214 uint64 i = (StartIndex + idx) % HazardChunkSize;
215 if (p->Records[i].Hazard.compare_exchange_weak(FreeEntry, Nullptr, std::memory_order_relaxed))
216 {
217 checkSlow(p->Records[i].GetHazard() == nullptr);
218 return &p->Records[i];
219 }
220 }
221 p = p->Next.load(std::memory_order_relaxed);
223 } while (p);
224
225 return Grow<Cached>();
226 }
227
228 //if we own the pointer
229 template<typename D>
230 inline void Delete(D* Pointer, int32 CollectLimit = -1)
231 {
232 if (Pointer)
233 {
235 }
236 }
237};
238
239
243template<typename H, bool Cached = false>
245{
246 THazardPointer(const THazardPointer&) = delete;
247 THazardPointer& operator=(const THazardPointer&) = delete;
248
249 std::atomic<H*>* Hazard = nullptr;
250 FHazardPointerCollection::FHazardRecord* Record = nullptr;
251
252public:
254 {
255 if (Record)
256 {
257 Record->Release();
258 }
259 Other.Hazard = nullptr;
260 Other.Record = nullptr;
261 }
262
264 {
265 if (Record)
266 {
267 Record->Release();
268 }
269 Hazard = Other.Hazard;
270 Record = Other.Record;
271 Other.Hazard = nullptr;
272 Other.Record = nullptr;
273 return *this;
274 }
275
276public:
277 THazardPointer() = default;
278
280 : Hazard(&InHazard), Record(Collection.Acquire<Cached>())
281 {
282 checkSlow(Record->GetHazard() == nullptr);
283 }
284
286 {
287 if (Record)
288 {
289 Record->Release();
290 }
291 }
292
293 //retireing can be used to release the hazardpointer without reaquiring a new Hazardslot in the Collection
294 inline void Retire()
295 {
297 Record->Retire();
298 }
299
300 //use with care, because the Hazardpointer will not protect anymore and needs to be recreated
301 inline void Destroy()
302 {
304 Record->Release();
305 Record = nullptr;
306 Hazard = nullptr;
307 }
308
309 inline H* Get() const
310 {
312 H* HazardPointer;
313 do
314 {
315 HazardPointer = (H*)Record->SetHazard(Hazard->load(std::memory_order_acquire));
316 } while (HazardPointer != Hazard->load(std::memory_order_acquire));
317 return HazardPointer;
318 }
319
320 inline bool IsValid()
321 {
322 return Hazard != nullptr && Record != nullptr;
323 }
324};
325
326template<typename H>
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define check(expr)
Definition AssertionMacros.h:314
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE::FPlatformRecursiveMutex FCriticalSection
Definition CriticalSection.h:53
THazardPointer< H, false > MakeHazardPointer(std::atomic< H * > &InHazard, FHazardPointerCollection &Collection)
Definition HazardPointer.h:327
uint32 GetThreadId(uint64 State)
Definition Ticker.cpp:34
uint32 Size
Definition VulkanMemory.cpp:4034
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition HazardPointer.h:58
void Delete(D *Pointer, int32 CollectLimit=-1)
Definition HazardPointer.h:230
CORE_API ~FHazardPointerCollection()
Definition HazardPointer.cpp:9
FHazardPointerCollection()
Definition HazardPointer.h:174
FHazardRecord * Acquire()
Definition HazardPointer.h:183
Definition HazardPointer.h:22
FHazardDeleter(void *InPointer)
Definition HazardPointer.h:27
FHazardDeleter(const FHazardDeleter &Other)
Definition HazardPointer.h:35
void * Pointer
Definition HazardPointer.h:26
FHazardDeleter & operator=(const FHazardDeleter &Other)
Definition HazardPointer.h:41
virtual void Delete()
Definition HazardPointer.h:29
bool operator==(const FHazardDeleter &Other) const
Definition HazardPointer.h:48
Definition Array.h:670
void Empty(SizeType Slack=0)
Definition Array.h:2273
Definition HazardPointer.h:245
~THazardPointer()
Definition HazardPointer.h:285
THazardPointer(THazardPointer &&Other)
Definition HazardPointer.h:253
H * Get() const
Definition HazardPointer.h:309
THazardPointer & operator=(THazardPointer &&Other)
Definition HazardPointer.h:263
void Destroy()
Definition HazardPointer.h:301
THazardPointer(std::atomic< H * > &InHazard, FHazardPointerCollection &Collection)
Definition HazardPointer.h:279
THazardPointer()=default
void Retire()
Definition HazardPointer.h:294
bool IsValid()
Definition HazardPointer.h:320
@ Delete
Definition PendingSpatialData.h:16
Definition HazardPointer.h:17
static uint32 AllocTlsSlot(void)
Definition AndroidPlatformTLS.h:30
static UE_FORCEINLINE_HINT void * Memcpy(void *Dest, const void *Src, SIZE_T Count)
Definition GenericPlatformMemory.h:591
static const uint32 InvalidTlsSlot
Definition GenericPlatformTLS.h:13
static FORCENOINLINE CORE_API void Free(void *Original)
Definition UnrealMemory.cpp:685