I have two very simple classes to illustrate the problem. MyMap has a TMap<FString, double> field and a setter and a getter for it. MyReadWriter has a MyMap field, and in every tick, it writes to it, then reads from it (with the same key). Apparently, placing MyReadWriter into the level, then starting the preview crashes the engine after a couple of seconds/minutes (different each time). As you’ll see from the code, I’ve tried to counter some potential race conditions - without success.
The crash report:
Unhandled Exception: EXCEPTION_ACCESS_VIOLATION writing address 0x0000000000000024
ntdll
ntdll
ntdll
UnrealEditor_DebugTMap!UMyMap::setValue() [C:\Users\ppffpp\Documents\Unreal Projects\DebugTMap\Source\DebugTMap\Private\MyMap.cpp:5]
UnrealEditor_DebugTMap!AMyReadWriter::Tick() [C:\Users\ppffpp\Documents\Unreal Projects\DebugTMap\Source\DebugTMap\Private\MyReadWriter.cpp:25]
UnrealEditor_Engine!AActor::TickActor() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\Actor.cpp:1516]
UnrealEditor_Engine!FActorTickFunction::ExecuteTick() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\Actor.cpp:251]
UnrealEditor_Engine!FTickFunctionTask::DoTask() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\TickTaskManager.cpp:278]
UnrealEditor_Engine!TGraphTask<FTickFunctionTask>::ExecuteTask() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Public\Async\TaskGraphInterfaces.h:1265]
UnrealEditor_Core!FNamedTaskThread::ProcessTasksNamedThread() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:758]
UnrealEditor_Core!FNamedTaskThread::ProcessTasksUntilQuit() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:649]
UnrealEditor_Core!FTaskGraphCompatibilityImplementation::WaitUntilTasksComplete() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:2125]
UnrealEditor_Engine!FTickTaskSequencer::ReleaseTickGroup() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\TickTaskManager.cpp:556]
UnrealEditor_Engine!FTickTaskManager::RunTickGroup() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\TickTaskManager.cpp:1583]
UnrealEditor_Engine!UWorld::RunTickGroup() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\LevelTick.cpp:771]
UnrealEditor_Engine!UWorld::Tick() [D:\build\++UE5\Sync\Engine\Source\Runtime\Engine\Private\LevelTick.cpp:1515]
UnrealEditor_UnrealEd!UEditorEngine::Tick() [D:\build\++UE5\Sync\Engine\Source\Editor\UnrealEd\Private\EditorEngine.cpp:1924]
UnrealEditor_UnrealEd!UUnrealEdEngine::Tick() [D:\build\++UE5\Sync\Engine\Source\Editor\UnrealEd\Private\UnrealEdEngine.cpp:531]
UnrealEditor!FEngineLoop::Tick() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp:5825]
UnrealEditor!GuardedMain() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Launch.cpp:188]
UnrealEditor!GuardedMainWrapper() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:118]
UnrealEditor!LaunchWindowsStartup() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:258]
UnrealEditor!WinMain() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:298]
UnrealEditor!__scrt_common_main_seh() [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
kernel32
ntdll
MyMap.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MyMap.generated.h"
/**
*
*/
UCLASS()
class DEBUGTMAP_API UMyMap : public UObject
{
GENERATED_BODY()
public:
void setValue(const FString & name, double value);
double getValue(const FString & name);
private:
TMap<FString, double> values;
FCriticalSection criticalSection;
};
MyMap.cpp
#include "MyMap.h"
void UMyMap::setValue(const FString & name, double value)
{
FScopeLock lock(&criticalSection);
values.Add(name, value);
}
double UMyMap::getValue(const FString & name)
{
FScopeLock lock(&criticalSection);
return values[name];
}
MyReadWriter.h
#pragma once
#include "MyMap.h"
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyReadWriter.generated.h"
UCLASS()
class DEBUGTMAP_API AMyReadWriter : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyReadWriter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
private:
UMyMap * myMap { nullptr };
};
MyReadWriter.cpp
#include "MyReadWriter.h"
// Sets default values
AMyReadWriter::AMyReadWriter()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("Root")));
}
// Called when the game starts or when spawned
void AMyReadWriter::BeginPlay()
{
Super::BeginPlay();
myMap = NewObject<UMyMap>();
}
// Called every frame
void AMyReadWriter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
myMap->setValue("asd", 1.0);
myMap->getValue("asd");
}
I have no idea what could go wrong in such simple code. Interestingly, if I use the exact same TMap<FString, double> field in MyReadWriter (instead of UMyMap * myMap), and called its methods directly from MyReadWriter::Tick, it seems like it is working well:
void AMyReadWriter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
values.Add("asd", 1.0);
double d { values["asd"] };
(void)d;
}
Thanks!