Files
UnrealEngine/Engine/Source/Runtime/CoreUObject/Tests/ClassDefaultObjectTest.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

89 lines
3.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#if WITH_LOW_LEVEL_TESTS
#include "ClassDefaultObjectTest.h"
#include "TestHarness.h"
#include "TestMacros/Assertions.h"
#include "Async/ManualResetEvent.h"
#include "Tasks/Task.h"
#include "UObject/Package.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ClassDefaultObjectTest)
TEST_CASE("UE::CoreUObject::GetDefaultObjectRace", "[CoreUObject][GetDefaultObjectRace]")
{
for (int32 Index = 0; Index < 1000; ++Index)
{
// Move old object out of the way and force recreation
if (UClassDefaultObjectTest* Obj = (UClassDefaultObjectTest*)UClassDefaultObjectTest::StaticClass()->GetDefaultObject(false))
{
Obj->Rename(nullptr, GetTransientPackage());
}
UClassDefaultObjectTest::StaticClass()->SetDefaultObject(nullptr);
std::atomic<bool> bContinue = true;
UE::FManualResetEvent StartedEvent;
int32 NumPostInitProperties = 0;
// One worker thread tries to query the default object if its ready.
UE::Tasks::FTask Task =
UE::Tasks::Launch(
TEXT("Task"),
[&]()
{
StartedEvent.Notify();
while (bContinue.load(std::memory_order_relaxed))
{
UClassDefaultObjectTest* Obj = (UClassDefaultObjectTest*)UClassDefaultObjectTest::StaticClass()->GetDefaultObject(false);
if (Obj)
{
// The flag is only safe to read if we have a barrier between the time where the default object pointer is
// published, and the time we read the flag. It needs to be paired with a release barrier when the default
// object is set.
std::atomic_thread_fence(std::memory_order_acquire);
TSAN_AFTER(Obj); // TSAN doesn't understand fence, help it understand what's going on
FPlatformProcess::YieldCycles(FMath::RandRange(0, 4000));
if (!Obj->HasAnyFlags(RF_NeedInitialization))
{
FPlatformProcess::YieldCycles(FMath::RandRange(0, 4000));
// This value is only safe to read if RF_NeedInitialization has been removed
// and that we applied the required barrier.
std::atomic_thread_fence(std::memory_order_acquire);
TSAN_AFTER(Obj); // TSAN doesn't understand fence, help it understand what's going on
NumPostInitProperties = Obj->NumPostInitProperties;
return;
}
}
}
}
);
// Make sure the async task has started
CHECK(StartedEvent.WaitFor(UE::FMonotonicTimeSpan::FromSeconds(1)));
CHECK(UClassDefaultObjectTest::StaticClass()->GetDefaultObject(false) == nullptr);
// Main thread creates the default object.
UClassDefaultObjectTest::StaticClass()->GetDefaultObject(true);
CHECK(Task.Wait(FTimespan::FromSeconds(1.0f)));
// Store the loop to kill the task if it failed to return on time
bContinue = false;
CHECK(Task.Wait(FTimespan::FromSeconds(1.0f)));
CHECK(NumPostInitProperties != 0);
}
// Move old object out of the way and force recreation
if (UClassDefaultObjectTest* Obj = (UClassDefaultObjectTest*)UClassDefaultObjectTest::StaticClass()->GetDefaultObject(false))
{
Obj->Rename(nullptr, GetTransientPackage());
}
UClassDefaultObjectTest::StaticClass()->SetDefaultObject(nullptr);
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
}
#endif // WITH_LOW_LEVEL_TESTS