Why does TMap crash the engine when I read and write it constantly through a UObject?

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!

Found the solution. The problem was that Unreal’s garbage collector deleted the object. To prevent this, the myMap variable must be annotated:

UPROPERTY()
UMyMap * myMap { nullptr };

And bind it to the owning object at creation:

myMap = NewObject<UMyMap>(this);

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.