UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
MTAccessDetector.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "CoreTypes.h"
7
8#define ENABLE_MT_DETECTOR DO_CHECK
9
10#if ENABLE_MT_DETECTOR
11
12#include "Containers/Array.h"
14#include "HAL/PlatformTLS.h"
17#include "Misc/Build.h"
18#include "AutoRTFM.h"
19#include <atomic>
20
23
24namespace UE::Private
25{
27
29 {
30 private:
36
37 friend struct ::FRWAccessDetector;
38 friend class ::FMRSWRecursiveAccessDetector;
39 friend class ::UE::Private::FMTAccessDetectorSuppressCheckFailuresForUnitTests;
40 };
41}
42
49{
50public:
51
53 : AtomicValue(0)
54 {}
55
57 {
58 checkf(AtomicValue.load(std::memory_order_relaxed) == 0, TEXT("Detector cannot be destroyed while other threads access it"));
59 }
60
62 {
63 checkf(Other.AtomicValue.load(std::memory_order_relaxed) == 0, TEXT("Detector cannot be \"moved out\" while other threads access it"));
64 }
65
67 {
68 checkf(AtomicValue.load(std::memory_order_relaxed) == 0, TEXT("Detector cannot be modified while other threads access it"));
69 checkf(Other.AtomicValue.load(std::memory_order_relaxed) == 0, TEXT("Detector cannot be \"moved out\" while other threads access it"));
70 return *this;
71 }
72
74 {
75 checkf(Other.AtomicValue.load(std::memory_order_relaxed) == 0, TEXT("Detector cannot be copied while other threads access it"));
76 }
77
79 {
80 checkf(AtomicValue.load(std::memory_order_relaxed) == 0, TEXT("Detector cannot be modified while other threads access it"));
81 checkf(Other.AtomicValue.load(std::memory_order_relaxed) == 0, TEXT("Detector cannot be copied while other threads access it"));
82 return *this;
83 }
84
89 inline bool AcquireReadAccess() const
90 {
91 const bool ErrorDetected = (AtomicValue.fetch_add(1, std::memory_order_relaxed) & WriterBits) != 0;
92 checkf(!ErrorDetected || UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure, TEXT("Acquiring a read access while there is already a write access"));
93 return !ErrorDetected;
94 }
95
100 inline bool ReleaseReadAccess() const
101 {
102 const bool ErrorDetected = (AtomicValue.fetch_sub(1, std::memory_order_relaxed) & WriterBits) != 0;
103 checkf(!ErrorDetected || UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure, TEXT("Another thread asked to have a write access during this read access"));
104 return !ErrorDetected;
105 }
106
111 inline bool AcquireWriteAccess() const
112 {
113 const bool ErrorDetected = AtomicValue.fetch_add(WriterIncrementValue, std::memory_order_relaxed) != 0;
114 checkf(!ErrorDetected || UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure, TEXT("Acquiring a write access while there are ongoing read or write access"));
115 return !ErrorDetected;
116 }
117
122 inline bool ReleaseWriteAccess() const
123 {
124 const bool ErrorDetected = AtomicValue.fetch_sub(WriterIncrementValue, std::memory_order_relaxed) != WriterIncrementValue;
125 checkf(!ErrorDetected || UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure, TEXT("Another thread asked to have a read or write access during this write access"));
126 return !ErrorDetected;
127 }
128
133 inline bool UpgradeReadAccessToWriteAccess() const
134 {
135 const bool bErrorDetected = AtomicValue.fetch_add(WriterIncrementValue, std::memory_order_relaxed) != 1;
136
138 {
139 checkf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure, TEXT("Upgrading a write access while there are ongoing read or write access"));
140 return false;
141 }
142
143 // Since we now hold the write lock, we can remove the read lock access we've upgraded to write lock access without re-checking!
144 AtomicValue -= 1;
145 return true;
146 }
147
148protected:
149
150 // We need to do an atomic operation to know there are multiple writers, this is why we reserve more than one bit for them.
151 // While firing the check upon acquire write access, the other writer thread could continue and hopefully fire a check upon releasing access so we get both faulty callstacks.
152 static constexpr uint32 WriterBits = 0xfff00000;
153 static constexpr uint32 WriterIncrementValue = 0x100000;
154
155 mutable std::atomic<uint32> AtomicValue;
156};
157
163{
164public:
169 inline bool AcquireWriteAccess() const
170 {
172
174 {
175 RecursiveDepth++;
176 return true;
177 }
178 else if (FRWAccessDetector::AcquireWriteAccess())
179 {
180 check(RecursiveDepth == 0);
182 RecursiveDepth++;
183 return true;
184 }
185 return false;
186 }
187
192 inline bool ReleaseWriteAccess() const
193 {
196 {
197 check(RecursiveDepth > 0);
198 RecursiveDepth--;
199
200 if (RecursiveDepth == 0)
201 {
203 return FRWAccessDetector::ReleaseWriteAccess();
204 }
205 return true;
206 }
207 else
208 {
209 // This can happen when a user continues pass a reported error,
210 // just trying to keep things going as best as possible.
211 return FRWAccessDetector::ReleaseWriteAccess();
212 }
213 }
214
215protected:
216 mutable uint32 WriterThreadID = (uint32)-1;
217 mutable int32 RecursiveDepth = 0;
218};
219
224{
225public:
230 inline bool AcquireReadAccess() const
231 {
234 {
235 return true;
236 }
237 return FRWAccessDetector::AcquireReadAccess();
238 }
239
244 inline bool ReleaseReadAccess() const
245 {
248 {
249 return true;
250 }
251 return FRWAccessDetector::ReleaseReadAccess();
252 }
253};
254
256{
257};
258
259template<typename RWAccessDetector>
261{
262public:
263
266 {
267 AccessDetector.AcquireReadAccess();
268 }
269
271 {
272 AccessDetector.ReleaseReadAccess();
273 }
274private:
276};
277
278template<typename RWAccessDetector>
280{
282}
283
284template<typename RWAccessDetector>
286{
287public:
290 {
291 AccessDetector.AcquireWriteAccess();
292 }
293
295 {
296 AccessDetector.ReleaseWriteAccess();
297 }
298private:
300};
301
302template<typename RWAccessDetector>
304{
306}
307
313{
314private:
315 // despite 0 is a valid TID on some platforms, we store actual TID + 1 to avoid collisions. it's required to use 0 as
316 // an invalid TID for zero-initilization
317 static constexpr uint32 InvalidThreadId = 0;
318
319 union FState
320 {
322
323 struct
324 {
325 uint32 ReaderNum : 20;
326 uint32 WriterNum : 12;
328 };
329
330 constexpr FState()
331 : Value(0)
332 {
333 static_assert(sizeof(FState) == sizeof(uint64)); // `FState` is stored in `std::atomic<uint64>`
334 }
335
336 explicit constexpr FState(uint64 InValue)
337 : Value(InValue)
338 {
339 }
340
345 {
346 }
347
349 {
350 return WriterThreadId - 1;
351 }
352
353 void SetWriterThreadId(uint32 ThreadId)
354 {
355 WriterThreadId = ThreadId + 1; // to shift from 0 TID that is considered "invalid"
356 }
357 };
358
359 // all atomic ops are relaxed to preserve the original memory order, as the detector is compiled out in non-dev builds
360 mutable std::atomic<uint64> State{ 0 }; // it's actually FState, but we need to do `atomic::fetch_add` on it
361
362 UE_FORCEINLINE_HINT FState LoadState() const
363 {
364 return FState(State.load(std::memory_order_relaxed));
365 }
366
367 UE_FORCEINLINE_HINT FState ExchangeState(FState NewState)
368 {
369 return FState(State.exchange(NewState.Value, std::memory_order_relaxed));
370 }
371
372 inline FState IncrementReaderNum() const
373 {
374 constexpr FState OneReader{ 1, 0, 0 };
375 return FState(State.fetch_add(OneReader.Value, std::memory_order_relaxed));
376 }
377
378 inline FState DecrementReaderNum() const
379 {
380 constexpr FState OneReader{ 1, 0, 0 };
381 return FState(State.fetch_sub(OneReader.Value, std::memory_order_relaxed));
382 }
383
384 UE_FORCEINLINE_HINT friend bool operator==(FState L, FState R)
385 {
386 return L.Value == R.Value;
387 }
388
389 inline static bool CheckOtherThreadWriters(FState InState)
390 {
391 if (InState.WriterNum != 0)
392 {
393 uint32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
394 if (InState.GetWriterThreadId() != CurrentThreadId)
395 {
396 ensureMsgf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure,
397 TEXT("Data race detected! Acquiring read access on thread %u concurrently with %d writers on thread %u:\nCurrent thread %u callstack:\n%s\nWriter thread %u callstack:\n%s"),
398 CurrentThreadId, InState.WriterNum, InState.GetWriterThreadId(),
399 CurrentThreadId, *GetCurrentThreadCallstack(),
400 InState.GetWriterThreadId(), *GetThreadCallstack(InState.GetWriterThreadId()));
401
402 return false;
403 }
404 }
405 return true;
406 }
407
408 static FString GetCurrentThreadCallstack()
409 {
410 const SIZE_T StackTraceSize = 65536;
411 ANSICHAR StackTrace[StackTraceSize] = { 0 };
412 FPlatformStackWalk::StackWalkAndDump(StackTrace, StackTraceSize, 0);
413 return ANSI_TO_TCHAR(StackTrace);
414 }
415
416 static FString GetThreadCallstack(uint32 ThreadId)
417 {
418 if (ThreadId == uint32(-1))
419 {
420 return TEXT("[none]");
421 }
422
423 // to get the callstack of another thread we suspend it first. it's a bad idea to suspend the current thread
424 if (FPlatformTLS::GetCurrentThreadId() == ThreadId)
425 {
427 }
428
429 const SIZE_T StackTraceSize = 65536;
430 ANSICHAR StackTrace[StackTraceSize] = { 0 };
431 FPlatformStackWalk::ThreadStackWalkAndDump(StackTrace, StackTraceSize, 0, ThreadId);
432 return ANSI_TO_TCHAR(StackTrace);
433 }
434
436// DestructionSentinel
437// This access detector supports its destruction (along with its owner) while being "accessed". If this is expected, acquire/release access
438// overloads must be used with a destruction sentinel stored on the callstack. If destroyed, `~FMRSWRecursiveAccessDetector()` will
439// clean up after the destroyed instance and notify the user (`FDestructionSentinel::bDestroyed`) that the release method can't be called as
440// the instance was destroyed.
441public:
442 enum class EAccessType { Reader, Writer };
443
445 {
447 : AccessType(InAccessType)
448 {
449 }
450
451 EAccessType AccessType;
452 const FMRSWRecursiveAccessDetector* Accessor;
453 bool bDestroyed = false;
454 };
455
456private:
457 // a TLS stack of destruction sentinels shared by all access detector instances on the callstack. the same access detector instance can have
458 // multiple destruction sentinels on the stack. the next "release access" with destruction sentinel always matches the top of the stack
460
463
464private:
466 // to support a write access from inside a read access on the same thread, we need to know that there're no readers on other threads.
467 // As we can't have TLS slot per access detector instance, a single one is shared between multiple instances with the assumption that
468 // there rarely will be more than one reader registered.
469 struct FReaderNum
470 {
471 const FMRSWRecursiveAccessDetector* Reader;
472 uint32 Num;
473 };
474
476
478
479 void RemoveReaderFromTls() const
480 {
481 int32 ReaderIndex = GetReadersTls().IndexOfByPredicate([this](FReaderNum ReaderNum) { return ReaderNum.Reader == this; });
482
484 TEXT("Either invalid usage of the access detector (no matching AcquireReadAccess()) or the access detector was trivially relocated (this instance is not registered): %u readers, %u writers on thread %u:\nCurrent thread %u callstack:\n%s"),
487
488 if (ReaderIndex == INDEX_NONE)
489 {
490 return; // to avoid asserting on the next line inside TArray
491 }
492
494 if (ReaderNum == 0)
495 {
497 }
498 }
500
501public:
503
505 // just default initialization, the copy is not being accessed
506 {
507 CheckOtherThreadWriters(Other.LoadState());
508 }
509
511 // do not alter the state, it can be accessed
512 {
513 CheckOtherThreadWriters(Other.LoadState());
514 return *this;
515 }
516
517 // use copy construction/assignment ^^ for move semantics too
518
520 {
521 // search for all destruction sentinels for this instance and remove them from the stack, while building an expected correct state of
522 // the access detector.
523 FState ExpectedState;
525 {
527 if (DestructionSentinel.Accessor == this)
528 {
529 DestructionSentinel.bDestroyed = true;
530 if (DestructionSentinel.AccessType == EAccessType::Reader)
531 {
532 ++ExpectedState.ReaderNum;
533
535 }
536 else
537 {
538 ++ExpectedState.WriterNum;
540 }
541
544 }
545 }
546
547 if (LoadState().Value != ExpectedState.Value)
548 {
549 ensureMsgf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure,
550 TEXT("Race detector destroyed while being accessed on another thread: %d readers, %d writers on thread %u:\nCurrent thread %u callstack:\n%s\nWriter thread %u callstack:\n%s"),
551 LoadState().ReaderNum - ExpectedState.ReaderNum,
555 }
556 }
557
558 inline bool AcquireReadAccess() const
559 {
560 FState PrevState = IncrementReaderNum();
561
563
564 // register the reader in TLS
565 int32 ReaderIndex = PrevState.ReaderNum == 0 ?
566 INDEX_NONE :
567 GetReadersTls().IndexOfByPredicate([this](FReaderNum ReaderNum) { return ReaderNum.Reader == this; });
568 if (ReaderIndex == INDEX_NONE)
569 {
570 GetReadersTls().Add(FReaderNum{this, 1});
571 }
572 else
573 {
574 ++GetReadersTls()[ReaderIndex].Num;
575 }
576
577 return bSuccess;
578 }
579
580 // an overload that handles access detector destruction from inside a read access, must be used along with the corresponding
581 // overload of `ReleaseReadAccess`
583 {
584 DestructionSentinel.Accessor = this;
586
587 return AcquireReadAccess();
588 }
589
590 inline bool ReleaseReadAccess() const
591 {
594 // no need to check for writers
595 return true;
596 }
597
598 // an overload that handles access detector destruction from inside a read access, must be used along with the corresponding
599 // overload of `AcquireReadAccess`
601 {
603
604 checkSlow(DestructionSentinel.Accessor == this);
605 checkfSlow(GetDestructionSentinelStackTls().Num() != 0, TEXT("An attempt to remove a not registered destruction sentinel"));
607
609
610 return bSuccess;
611 }
612
613 inline bool AcquireWriteAccess()
614 {
615 FState LocalState = LoadState();
616 bool bSuccess = true;
617
618 if (LocalState.ReaderNum >= 1)
619 { // check that all readers are on the current thread
620 int32 ReaderIndex = GetReadersTls().IndexOfByPredicate([this](FReaderNum ReaderNum) { return ReaderNum.Reader == this; });
621
622 if (ReaderIndex == INDEX_NONE)
623 {
624 ensureMsgf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure,
625 TEXT("Either a race detected (%u reader(s) on another thread(s) while acquiring write access on the current thread) or the access detector was trivially relocated:\nCurrent thread %u callstack:\n%s"),
627
628 return false; // to avoid asserting on the next line inside TArray
629 }
630
631 if (GetReadersTls()[ReaderIndex].Num != LocalState.ReaderNum)
632 {
633 ensureMsgf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure,
634 TEXT("Data race detected: %d reader(s) on another thread(s) while acquiring write access.\nCurrent thread %u callstack:\n%s"),
636
637 bSuccess = false;
638 }
639 }
640
641 uint32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
642 if (LocalState.WriterNum != 0)
643 {
644 if (LocalState.GetWriterThreadId() != CurrentThreadId)
645 {
646 ensureMsgf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure,
647 TEXT("Data race detected: acquiring write access on thread %u concurrently with %d writers on thread %u:\nCurrent thread %u callstack:\n%s\nWriter thread %u callstack:\n%s"),
648 CurrentThreadId, LocalState.WriterNum, LocalState.GetWriterThreadId(),
649 CurrentThreadId, *GetCurrentThreadCallstack(),
650 LocalState.GetWriterThreadId(), *GetThreadCallstack(LocalState.GetWriterThreadId()));
651
652 bSuccess = false;
653 }
654 }
655
656 FState NewState{ LocalState.ReaderNum, LocalState.WriterNum + 1u, CurrentThreadId + 1 };
657 FState PrevState = ExchangeState(NewState);
658
659 if (LocalState != PrevState)
660 {
661 ensureMsgf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure,
662 TEXT("Data race detected: other thread(s) activity during acquiring write access on thread %u: %u -> %u readers, %u -> %u writers on thread %u -> %u:\nCurrent thread %u callstack:\n%s\nWriter thread %u callstack:\n%s"),
663 CurrentThreadId,
664 LocalState.ReaderNum, PrevState.ReaderNum,
665 LocalState.WriterNum, PrevState.WriterNum,
666 LocalState.GetWriterThreadId(), PrevState.GetWriterThreadId(),
667 CurrentThreadId, *GetCurrentThreadCallstack(),
668 PrevState.GetWriterThreadId(), *GetThreadCallstack(PrevState.GetWriterThreadId()));
669
670 bSuccess = false;
671 }
672
673 return bSuccess;
674 }
675
676 // an overload that handles access detector destruction from inside a write access, must be used along with the corresponding
677 // overload of `ReleaseWriteAccess`
679 {
680 DestructionSentinel.Accessor = this;
682
683 return AcquireWriteAccess();
684 }
685
686 inline bool ReleaseWriteAccess()
687 {
688 FState LocalState = LoadState();
689
690 uint32 WriterThreadId = LocalState.WriterNum != 1 ? LocalState.WriterThreadId : InvalidThreadId;
691 FState NewState{ LocalState.ReaderNum, LocalState.WriterNum - 1u, WriterThreadId };
692 FState PrevState = ExchangeState(NewState);
693
694 if (LocalState != PrevState)
695 {
696 ensureMsgf(UE::Private::FMTAccessDetectorOptions::bSuppressCheckFailure,
697 TEXT("Data race detected: other thread(s) activity during releasing write access on thread %d: %u -> %u readers, %u -> %u writers on thread %u -> %u\nCurrent thread %u callstack:\n%s\nWriter thread %u callstack:\n%s"),
699 LocalState.ReaderNum, PrevState.ReaderNum,
700 LocalState.WriterNum, PrevState.WriterNum,
701 LocalState.GetWriterThreadId(), PrevState.GetWriterThreadId(),
703 PrevState.GetWriterThreadId(), *GetThreadCallstack(PrevState.GetWriterThreadId()));
704
705 return false;
706 }
707
708 return true;
709 }
710
711 // an overload that handles access detector destruction from inside a write access, must be used along with the corresponding
712 // overload of `AcquireWriteAccess`
714 {
716
718 checkSlow(DestructionSentinel.Accessor == this);
719 checkfSlow(GetDestructionSentinelStackTls().Num() != 0, TEXT("An attempt to remove a not registered destruction sentinel"));
721
723
724 return bSuccess;
725 }
726};
727
729
730#define UE_MT_DECLARE_RW_ACCESS_DETECTOR(AccessDetector) FRWAccessDetector AccessDetector
731#define UE_MT_DECLARE_RW_RECURSIVE_ACCESS_DETECTOR(AccessDetector) FRWRecursiveAccessDetector AccessDetector
732#define UE_MT_DECLARE_RW_FULLY_RECURSIVE_ACCESS_DETECTOR(AccessDetector) FRWFullyRecursiveAccessDetector AccessDetector
733#define UE_MT_DECLARE_MRSW_RECURSIVE_ACCESS_DETECTOR(AccessDetector) FMRSWRecursiveAccessDetector AccessDetector
734
735#define UE_MT_SCOPED_READ_ACCESS(AccessDetector) const FBaseScopedAccessDetector& PREPROCESSOR_JOIN(ScopedMTAccessDetector_,__LINE__) = MakeScopedReaderAccessDetector(AccessDetector)
736#define UE_MT_SCOPED_WRITE_ACCESS(AccessDetector) const FBaseScopedAccessDetector& PREPROCESSOR_JOIN(ScopedMTAccessDetector_,__LINE__) = MakeScopedWriterAccessDetector(AccessDetector)
737
738#define UE_MT_ACQUIRE_READ_ACCESS(AccessDetector) (AccessDetector).AcquireReadAccess()
739#define UE_MT_RELEASE_READ_ACCESS(AccessDetector) (AccessDetector).ReleaseReadAccess()
740#define UE_MT_ACQUIRE_WRITE_ACCESS(AccessDetector) (AccessDetector).AcquireWriteAccess()
741#define UE_MT_RELEASE_WRITE_ACCESS(AccessDetector) (AccessDetector).ReleaseWriteAccess()
742
743#else // ENABLE_MT_DETECTOR
744
745#define UE_MT_DECLARE_RW_ACCESS_DETECTOR(AccessDetector)
746#define UE_MT_DECLARE_RW_RECURSIVE_ACCESS_DETECTOR(AccessDetector)
747#define UE_MT_DECLARE_RW_FULLY_RECURSIVE_ACCESS_DETECTOR(AccessDetector)
748#define UE_MT_DECLARE_MRSW_RECURSIVE_ACCESS_DETECTOR(AccessDetector)
749
750#define UE_MT_SCOPED_READ_ACCESS(AccessDetector)
751#define UE_MT_SCOPED_WRITE_ACCESS(AccessDetector)
752
753#define UE_MT_ACQUIRE_READ_ACCESS(AccessDetector)
754#define UE_MT_RELEASE_READ_ACCESS(AccessDetector)
755#define UE_MT_ACQUIRE_WRITE_ACCESS(AccessDetector)
756#define UE_MT_RELEASE_WRITE_ACCESS(AccessDetector)
757
758#endif // ENABLE_MT_DETECTOR
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define checkfSlow(expr, format,...)
Definition AssertionMacros.h:333
#define check(expr)
Definition AssertionMacros.h:314
#define ensureMsgf( InExpression, InFormat,...)
Definition AssertionMacros.h:465
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
bool bSuccess
Definition ConvexDecomposition3.cpp:819
@ INDEX_NONE
Definition CoreMiscDefines.h:150
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::SIZE_T SIZE_T
An unsigned integer the same size as a pointer, the same as UPTRINT.
Definition Platform.h:1150
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
#define UE_FORCEINLINE_HINT
Definition Platform.h:723
#define UNLIKELY(x)
Definition Platform.h:857
FPlatformTypes::ANSICHAR ANSICHAR
An ANSI character. Normally a signed type.
Definition Platform.h:1131
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
@ Num
Definition MetalRHIPrivate.h:234
#define ANSI_TO_TCHAR(str)
Definition StringConv.h:1020
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition Array.h:670
void LoadState(const FString &IniName)
Definition Scalability.cpp:1172
bool operator==(const FCachedAssetKey &A, const FCachedAssetKey &B)
Definition AssetDataMap.h:501
State
Definition PacketHandler.h:88
Definition PackageReader.cpp:44
static uint32 GetCurrentThreadId(void)
Definition AndroidPlatformTLS.h:20
static void ThreadStackWalkAndDump(ANSICHAR *HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, uint32 ThreadId)
Definition GenericPlatformStackWalk.h:270
CORE_API static FORCENOINLINE void StackWalkAndDump(ANSICHAR *HumanReadableString, SIZE_T HumanReadableStringSize, int32 IgnoreCount, void *Context=nullptr)
Definition GenericPlatformStackWalk.cpp:178