We have met an issue when we switch maps in the game. The garbage collection fall into infinite loop in the second round of IncrementalPurgeGarbage (GarbageCollection.cpp line 1003-1085). The infinite loop code is:
if( !GObjCurrentPurgeObjectIndex )
{
UE_LOG(LogGarbage, Log, TEXT("Second round incremental GC, since there are some object is deferred. \
There are %d GGCObjectsPendingDestruction."), GGCObjectsPendingDestructionCount);
// We've finished iterating over all unreachable objects, but we need still need to handle
// objects that were deferred.
while( GGCObjectsPendingDestructionCount > 0 )
{
int32 CurPendingObjIndex = 0;
while( CurPendingObjIndex < GGCObjectsPendingDestructionCount )
{
......
}
if( bUseTimeLimit )
{
// A time limit is set and we've completed a full iteration over all leftover objects, so
// go ahead and bail out even if we have more time left or objects left to process. It's
// likely in this case that we're waiting for the render thread.
break;
}
else if( GGCObjectsPendingDestructionCount > 0 )
{
// Sleep before the next pass to give the render thread some time to release fences.
FPlatformProcess::Sleep( 0 );
}
}
// Have all objects been destroyed now?
if( GGCObjectsPendingDestructionCount == 0 )
{
// Release memory we used for objects pending destruction, leaving some slack space
GGCObjectsPendingDestruction.Empty( 256 );
// Destroy has been routed to all objects so it's safe to delete objects now.
......
}
}
When this issue happens, the variable GGCObjectsPendingDestructionCount
always equal to 1, the type of Object
in the code above is UTexture2D
, its PendingMipChangeRequestStatus
always equal to 3 (TextState_Inprogress_Upload
). We have tried to find all the places that set this status and log out the results, all results showed the status would changed to 2 eventually. From the loop, it seems caused by the texture object can
not finish uploading to GPU so that GC can not release it. But we can not find any hint where and why this issue happens. Anyone has idea about this?
Note: We use ServerTravel
when we switch maps. Here’s the code
void UShooterGameInstance::ServerChangeMap(const FString& MapName)
{
UWorld* const World = GetWorld();
if (!World)
{
return;
}
FString FullMapName = TEXT("/Game/Maps/") + MapName;
World->ServerTravel(FullMapName);
}
The log before infinite loop is:
[2017.06.29-10.32.51:815][369]LogTexture: Got work from async thread for creating texture. The resource is /Game/UI/Textures/T_result_win.T_result_win
[2017.06.29-10.32.51:816][369]LogTexture: Finish async thread for creating texture. The resource is /Game/UI/Textures/T_result_win.T_result_win
[2017.06.29-10.32.51:816][369]LogTexture: Finish async thread for creating texture and after barrier. The resource is /Game/UI/Textures/T_result_win.T_result_win
[2017.06.29-10.32.51:816][369]LogTexture: Finish async thread for creating texture and request state should changed to 2. The resource is /Game/UI/Textures/T_result_win.T_result_win. The threadSafeCounter is 2
[2017.06.29-10.32.56:707][333]LogWorld: SeamlessTravel to: /Game/Maps/GunWorks
[2017.06.29-10.32.56:717][334]LogGarbage: Collecting garbage
[2017.06.29-10.32.56:738][334]LogGarbage: 0.253875 ms for GC
[2017.06.29-10.32.56:742][334]LogGarbage: 4.053824 ms for unhashing unreachable objects. Clusters removed: 20.
[2017.06.29-10.32.56:745][334]LogGarbage: Second round incremental GC, since there are some object is deferred. There are 1 GGCObjectsPendingDestruction.