Summary
Editor crashes on exit (EXCEPTION_ACCESS_VIOLATION in UWorld::CleanupWorld) after a Hot Reload that changes the layout of a UWorldSubsystem class.
When a UWorldSubsystem’s header changes (e.g. a member is added) and the module is Hot Reloaded, the reload re-instances the existing subsystem object (Re-instancing <Class> after reload. in the log).
FSubsystemCollectionBase::SubsystemMap is reported to GC (AddStableReferenceMap), so reference replacement updates it to the new instance. The per-class cache FSubsystemCollectionBase::SubsystemArrayMap (TMap<UClass*, TUniquePtr<FSubsystemArray>>, holding raw TArray<USubsystem*>) is not exposed to GC and is not updated. It keeps pointing at the old instance, which is later garbage collected.
On editor exit, UWorld::CleanupWorldInternal calls SubsystemCollection.ForEachSubsystem(... PreDeinitialize ...). That iterates the stale SubsystemArrayMap entry and calls IsA() on the freed object, which crashes.
What Type of Bug are you experiencing?
Framework
Steps to Reproduce
- Create a C++ project and add a
UWorldSubsystemsubclass (any trivial implementation). - Open the project in the Editor so the subsystem is created for the editor world.
- Add a member variable to the subsystem’s header (a layout change).
- Hot Reload the module from inside the Editor (not Live Coding). The log shows
1 class changedandRe-instancing <Class> after reload. - Close the Editor.
Expected Result
The Editor closes normally.
Observed Result
The Editor crashes during exit with EXCEPTION_ACCESS_VIOLATION in UStruct::IsChildOf, called from FSubsystemCollectionBase::ForEachSubsystemOfClass in UWorld::CleanupWorldInternal. It reproduces every time. A .cpp-only change (no class layout change) does not crash.
Affects Versions
5.8
Platform(s)
Windows
For crash reports, include your callstack
Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x000000003f800038
UnrealEditor_CoreUObject!UStruct::IsChildOf() [Runtime\CoreUObject\Private\UObject\Class.cpp:2744]
UnrealEditor_Engine!UObjectBaseUtility::IsA<UClass *>() [Runtime\CoreUObject\Public\UObject\UObjectBaseUtility.h:650]
UnrealEditor_Engine!TFunctionRefCaller<FObjectSubsystemCollection<UWorldSubsystem>::ForEachSubsystem::<lambda_1>,void,USubsystem *>::Call() [Runtime\Core\Public\Templates\Function.h:292]
UnrealEditor_Engine!FSubsystemCollectionBase::ForEachSubsystemOfClass() [Runtime\Engine\Private\Subsystems\SubsystemCollection.cpp:179]
UnrealEditor_Engine!UWorld::CleanupWorldInternal() [Runtime\Engine\Private\World.cpp:6476]
UnrealEditor_Engine!UWorld::CleanupWorld() [Runtime\Engine\Private\World.cpp:6241]
UnrealEditor_UnrealEd!UEditorEngine::PreExit() [Editor\UnrealEd\Private\EditorEngine.cpp:1624]
UnrealEditor!FEngineLoop::Exit() [Runtime\Launch\Private\LaunchEngineLoop.cpp:5065]
UnrealEditor!GuardedMain() [Runtime\Launch\Private\Launch.cpp:204]
UnrealEditor!GuardedMainWrapper() [Runtime\Launch\Private\Windows\LaunchWindows.cpp:123]
UnrealEditor!LaunchWindowsStartup() [Runtime\Launch\Private\Windows\LaunchWindows.cpp:277]
UnrealEditor!WinMain() [Runtime\Launch\Private\Windows\LaunchWindows.cpp:338]
Additional Notes
Suggested fix: rebuild or clear SubsystemArrayMap after reinstancing (for example on FCoreUObjectDelegates::ReloadReinstancingCompleteDelegate), or update its entries through the same reference replacement as SubsystemMap.
Related: after the same kind of reload, the collection can also create a second instance from the new class while the re-instanced one still exists (this spawned duplicate gameplay actors in our project). Returning false from ShouldCreateSubsystem when GetClass() has CLASS_NewerVersionExists avoids the duplicate, but the exit crash remains.
Workaround: restart the Editor after changing a UWorldSubsystem header instead of Hot Reloading it.
Environment: UE 5.8 Launcher build, Windows 11.