Summary
After a normal shutdown (the log ends with LogExit: Exiting. and Log file closed), the editor process intermittently terminates with an access violation (exit code -1073741819 / 0xC0000005). It reproduces on 5.8.4 with a project that has no game code. The faulting thread is in the heap free path (ntdll!RtlFreeHeap ← ucrtbase!free) during static destruction: a Slate async task notification widget, kept alive by an element of FTSTicker::GetCoreTicker(), is destroyed after FInternationalization::TearDown(), when the ICU allocator hooks have already been reset.
What Type of Bug are you experiencing?
Editor
Steps to Reproduce
- Create a project whose
.uprojecthas no plugins of its own, withEditorStartupMap=/Engine/Maps/Entry. - Add a Python script that waits about 30 frames via
unreal.register_slate_post_tick_callbackand then callsunreal.SystemLibrary.quit_editor(). - Run the editor 24 times consecutively with:
UnrealEditor.exe <project>.uproject -unattended -nosplash -nosound -nop4 -NoLiveCoding -culture=en -stdout -FullStdOutLogOutput -EnablePlugins=PythonScriptPlugin -ExecutePythonScript="<path>/control_close_engine.py" -RenderOffscreen -ResX=1280 -ResY=720 -d3d12 - Record the process exit code of each run (we used a native debugger that records second-chance exceptions, with
_NO_DEBUG_HEAP=1; any equivalent debugger works).
Expected Result
Every run exits with code 0 after “Log file closed”.
Observed Result
4 of 24 consecutive runs on 5.8.4 CL58902052 exit with 0xC0000005 after “Log file closed” (an earlier 5.8.3 series gave 10 of 24, conditions not recorded, indicative only). Faulting frames: ntdll!RtlFreeHeap / ucrtbase!free (heap code at ntdll+0x9750 or ntdll+0x94C3, called from ntdll+0x53E2F), once in UnrealEditor-Slate.dll+0x1CA6F4. In our game project an interactive editor session that plays a PIE map and quits fails 4 of 10 times with the same frames.
Project-side workaround: a named FAsyncTaskNotificationFactory that creates FCoreAsyncTaskNotificationImpl (no Slate widgets), enabled by default with -unattended. With it: 0 crashes in 50 unattended runs, against 8 in 84 without it (Fisher exact, two-sided p ≈ 0.025). It does not cover interactive sessions or notifications created before our module loads, so the root cause is in the engine.
Affects Versions
5.8
Platform(s)
Windows
For crash reports, include your callstack
Reconstructed from a 5.8.3 minidump (no PDB; offline StackWalk64 with captured memory, 57 frames; names reconstructed from disassembly and engine source). On 5.8.4 the captured stacks stop at the CRT with the same top frames.
ntdll+0x9750 (access violation) <- RtlFreeHeap
ucrtbase+0x12D7B free
UnrealEditor-Core: uprv_free (ICU; all four ICU memory hooks NULL) <- ubidi_close
UnrealEditor-Core: FICUTextBiDi::~FICUTextBiDi
UnrealEditor-Slate: FTextLayout / FSlateTextBlockLayout destruction
UnrealEditor-SlateCore / Slate: STextBlock -> SNotificationItem widget tree release (nested SWidget destructors)
UnrealEditor-Slate: TSharedPtr<SNotificationItem> captured by FSlateAsyncTaskNotificationImpl::UpdateNotificationDeferred
UnrealEditor-Core: FTSTicker::FElement / AddedElements queue destruction
UnrealEditor-Core: static destruction of FTSTicker::GetCoreTicker() (after AppExit)
Additional Notes
Engine: Unreal Engine 5.8.4 CL58902052 (Launcher build, ++UE5+Release-5.8, no engine modifications); also seen on 5.8.3.
System: Windows 11 (10.0.26300) x64, AMD Ryzen 9 5900X, NVIDIA GeForce RTX 3080, D3D12, MSVC 14.51.
Suspected cause (5.8 release branch):
Slate/Private/Framework/Notifications/SlateAsyncTaskNotificationImpl.cpp:UpdateNotification()enqueuesUpdateNotificationDeferredonFTSTicker::GetCoreTicker(), capturing theSNotificationItem, with no check for engine exit (IsEngineExitRequested()).AddTickeris thread-safe, so a worker thread can enqueue after the ticker reset.Launch/Private/LaunchEngineLoop.cpp:FEngineLoop::Exit()resets the CoreTicker only once, at the beginning; elements added later (Slate shutdown, module shutdown) survive until static destruction, afterAppExit()has torn down internationalization.ICUInternationalization.cpp/ICUText.cpp:u_cleanup()resets the ICU allocator hooks; theFICUTextBiDidestructor then callsubidi_close, which frees with CRTfree.
Possible fixes: do not enqueue deferred Slate updates once engine exit has started; reset the CoreTicker again after Slate and module shutdown; or release staged notifications before ICU teardown.
A synthetic test that completes a Slate notification inside FCoreDelegates::OnPreExit did not crash in 10 runs, so the exact late producer is not yet identified. Minidumps, the reproducer project source and sanitized run manifests are available on request through a private channel.