Crash in EnvQuerySystem due to GC

I am getting the following crash

Unhandled Exception: SIGSEGV: invalid attempt to read memory at address 0x00000001b70f04f8

[22.28.19:118][0066.02][871]LogCore: Fatal error!

FEnvQueryInstance::ExecuteOneStep() @ EnvQueryInstance.cpp:445
UEnvQueryManager::Tick() @ EnvQueryManager.cpp:481
TGraphTask<FTickFunctionTask>::ExecuteTask() @ TaskGraphInterfaces.h:1235
FNamedTaskThread::ProcessTasksNamedThread() @ TaskGraph.cpp:774
FNamedTaskThread::ProcessTasksUntilQuit() @ TaskGraph.cpp:664
FTaskGraphCompatibilityImplementation::WaitUntilTasksComplete() @ TaskGraph.cpp:2144
FTickTaskSequencer::ReleaseTickGroup() @ TickTaskManager.cpp:603
FTickTaskManager::RunTickGroup() @ TickTaskManager.cpp:1656
UWorld::RunTickGroup() @ LevelTick.cpp:810
UWorld::Tick() @ LevelTick.cpp:1597

I believe it’s because the EQS Generator is getting GC’d just before hand.

With VeryVerbose GC logs we get this JUST before the crash

[22.28.15:231][0062.13][813]LogGarbage: Collecting garbage
[22.28.15:231][0062.13][813]LogGarbage: Verbose: 0.350189 ms for MarkObjectsAsUnreachable Phase (20 Objects To Serialize)
[22.28.15:242][0062.14][813]LogGarbage: Verbose: 11.008596 ms for Reachability Analysis
[22.28.15:242][0062.14][813]LogGarbage: GC Reachability Analysis total time: 11.41 ms (11.41 ms on reference traversal)
[22.28.15:242][0062.14][813]LogGarbage: 11.41 ms for GC - 30527 refs/ms while processing 348230 references from 38290 objects  with 491 clusters
[22.28.15:242][0062.14][813]LogGarbage: 0.010562 ms for Dissolve Unreachable Clusters (0/491 clusters dissolved containing 0 cluster objects)
[22.28.15:242][0062.14][813]LogGarbage: 0.344590 ms for Gather Unreachable Objects (9016 objects collected / 66718 scanned with 1 thread(s))
[22.28.15:292][0062.19][814]LogStreaming: Display: 0.065 ms for processing 9016 objects in RemoveUnreachableObjects(Queued=0, Async=0). Removed 10 (4403->4393) packages and 34 (10071->10037) public exports.
[22.28.15:300][0062.20][814]LogGarbage: Starting unhashing unreachable objects (9016 objects to unhash).
[22.28.15:498][0062.40][817]LogGarbage: Finished unhashing unreachable objects (9016 objects unhashed).
[22.28.15:499][0062.40][817]LogGarbage: 1.058 ms for incrementally purging unreachable objects (FinishDestroyed: 2131, Destroyed: 0 / 9016)
[22.28.15:566][0062.47][818]LogGarbage: 2.002 ms for incrementally purging unreachable objects (FinishDestroyed: 6902, Destroyed: 0 / 9016)
[22.28.15:633][0062.53][819]LogGarbage: 2.010 ms for incrementally purging unreachable objects (FinishDestroyed: 9016, Destroyed: 2600 / 9016)
[22.28.15:700][0062.60][820]LogGarbage: 2.028 ms for incrementally purging unreachable objects (FinishDestroyed: 9016, Destroyed: 7900 / 9016)
[22.28.15:765][0062.66][821]LogGarbage: GC purged 9016 objects (66718 -> 57702) in 0.572ms
CommonUnixCrashHandler: Signal=11
[22.28.19:118][0066.02][871]LogCore: === Critical error: ===

Some background: the dangling pointer access via a raw pointer. This raw pointer is a copy of a smart pointer allocated in UEnvQueryManager::CreateQueryInstance at ~EnvQueryManager.cpp:859. The copy from smart → raw happens in a subsequent call to CreateOptionInstance.

So the raw pointer should be safe, since it points to a UObject who’s life should be gauranteed by the following hierarchy:

UPROPERTY(Transient)
TObjectPtr<class UAISystemBase>        UWorld::AISystem;

UPROPERTY(Transient)
TObjectPtr<UEnvQueryManager>           UAISystem::EnvironmentQueryManager;

UPROPERTY(Transient)  
TArray<FEnvQueryInstanceCache>        UEnvQueryManager::InstanceCache;

UPROPERTY()
TObjectPtr<UEnvQuery>                 FEnvQueryInstanceCache::Template

UPROPERTY()
TArray<TObjectPtr<UEnvQueryOption>>   UEnvQuery::Options;
 
UPROPERTY()	
TObjectPtr<UEnvQueryGenerator>        UEnvQueryOption::Generator;  <-- This becomes dangling

For reference I am running Client with a Linux Server build. The server is a Linux build. Engine version 5.4.4.

I am not sure how to further investigate this. Any help would be appreciated.

Hi, in windows I would run the game from visual studio in debug mode. Then when it crashes you get exactly where it crashes, can see the call trace leading to the crash and what values the variables have (and can also set breakpoints).