If in a dedicated server we call FPlatformMisc::RequestExit(false) (in the game thread or in a delegate not running in the game thread), in ~TLockFreeFixedSizeAllocator, GetNumUsed returns different to 0 and the server crashes in check(GetNumUsed() == 0);
This seems to be a leak and investigating (adding some leak tracker code in that class) we got this allocate callstack that never gets freed:
[0] FUnixPlatformStackWalk::CaptureStackBackTrace(unsigned long long*, unsigned int, void*) (Engine/Source/./Runtime/Core/Private/Unix/UnixPlatformStackWalk.cpp:806)
[1] TLockFreeFixedSizeAllocator<65536, 64, FThreadSafeCounter>::Allocate(int) (Engine/Source/Runtime/Core/Public/Containers/LockFreeFixedSizeAllocator.h:339)
[2] FPageAllocator::Alloc(int) (Engine/Source/./Runtime/Core/Private/Misc/MemStack.cpp:242)
[3] TLinearAllocatorBase<FDefaultBlockAllocationTag, (ELinearAllocatorThreadPolicy)0>::AllocateBlock(TLinearAllocatorBase<FDefaultBlockAllocationTag, (ELinearAllocatorThreadPolicy)0>::FBlockHeader*&) (Engine/Source/Runtime/Core/Public/Experimental/ConcurrentLinearAllocator.h:321)
[4] TLinearAllocatorBase<FDefaultBlockAllocationTag, (ELinearAllocatorThreadPolicy)0>::Malloc(unsigned long, unsigned int) (Engine/Source/Runtime/Core/Public/Experimental/ConcurrentLinearAllocator.h:424)
[5] FRenderCommandList::Create(ERenderCommandListFlags, FMemStackBase::EPageSize) (Engine/Source/Runtime/RenderCore/Public/RenderingThread.h:845)
[6] UWorld::SendAllEndOfFrameUpdatesInternal(UWorld::EEndOfFrameUpdateLocation) (Engine/Source/./Runtime/Engine/Private/LevelTick.cpp:1224)
[7] UWorld::SendAllEndOfFrameUpdates() (Engine/Source/./Runtime/Engine/Private/LevelTick.cpp:1350)
[8] UEngine::SendWorldEndOfFrameUpdates() (Engine/Source/./Runtime/Engine/Private/UnrealEngine.cpp:1759)
[9] UEngine::PreGarbageCollect() (Engine/Source/Runtime/Core/Public/ProfilingDebugging/CpuProfilerTrace.h:236)
[10] TBaseStaticDelegateInstance<void (), FDefaultDelegateUserPolicy>::ExecuteIfSafe() const (Engine/Source/Runtime/Core/Public/Delegates/DelegateInstancesImpl.h:827)
[11] void TMulticastDelegateBase<FDefaultDelegateUserPolicy>::Broadcast<IBaseDelegateInstance<void (), FDefaultDelegateUserPolicy> >() const (Engine/Source/Runtime/Core/Public/Delegates/MulticastDelegateBase.h:301)
[12] void UE::GC::PreCollectGarbageImpl<false>(EObjectFlags) (Engine/Source/Runtime/Core/Public/ProfilingDebugging/CpuProfilerTrace.h:236)
[13] UE::GC::FReachabilityAnalysisState::PerformReachabilityAnalysisAndConditionallyPurgeGarbage(bool) (Engine/Source/./Runtime/CoreUObject/Private/UObject/GarbageCollection.cpp:5872)
[14] TryCollectGarbage(EObjectFlags, bool) (Engine/Source/./Runtime/CoreUObject/Private/UObject/GarbageCollection.cpp:5527)
[15] UEngine::PerformGarbageCollectionAndCleanupActors() (Engine/Source/./Runtime/Engine/Private/UnrealEngine.cpp:2034)
[16] UEngine::ConditionalCollectGarbage() (Engine/Source/./Runtime/Engine/Private/UnrealEngine.cpp:1982)
[17] UWorld::Tick(ELevelTick, float) (Engine/Source/Runtime/Core/Public/ProfilingDebugging/CpuProfilerTrace.h:236)
[18] UGameEngine::Tick(float, bool) (Engine/Source/./Runtime/Engine/Private/GameEngine.cpp:1883)
[19] FEngineLoop::Tick() (Engine/Source/Runtime/Core/Public/ProfilingDebugging/CpuProfilerTrace.h:236)
[20] GuardedMain(char16_t const*) (Engine/Source/./Runtime/Launch/Private/Launch.cpp:190)
[21] CommonUnixMain(int, char**, int (*)(char16_t const*), void (*)()) (Engine/Source/./Runtime/Unix/UnixCommonStartup/Private/UnixCommonStartup.cpp:323)
The interesting line is: UWorld::SendAllEndOfFrameUpdatesInternal(UWorld::EEndOfFrameUpdateLocation) - LevelTick.cpp:1224, which in our version is:
FRenderCommandList* RootRenderCommandList = FRenderCommandList::Create(ERenderCommandListFlags::CloseOnSubmit);
It seems this is never properly removed.
What is the best approach to avoid this happening?
We have implemented this quick workaround, that works, but we are unsure it is the right fix for this situation:
FRenderCommandList* RootRenderCommandList = nullptr;
TOptional<FRenderCommandList::FRecordScope> RecordScope;
if (!IsRunningDedicatedServer())
{
RootRenderCommandList = FRenderCommandList::Create(ERenderCommandListFlags::CloseOnSubmit);
RecordScope.Emplace(RootRenderCommandList, FRenderCommandList::EStopRecordingAction::Submit);
}
Thank you,
Jose
[Attachment Removed]