Description

Context

ActorComponents can schedule an end-of-frame update for themselves by calling UWorld::MarkActorComponentForNeededEndOfFrameUpdate. The UWorld will iterate all those actor components once per tick in:

UWorld::SendAllEndOfFrameUpdates() 

which will iterate various actor component arrays of UWorld and call virtual functions like:

Component->OnPreEndOfFrameSync(); 

Those component arrays should not be modified during iteration, at least according to how the iteration is currently implemented, for example:

for (UActorComponent* Component : ComponentsThatNeedPreEndOfFrameSync)
{
    if (Component)
    {
        ...
        // I.e: inside OnPreEndOfFrameSync ComponentsThatNeedPreEndOfFrameSync should not be modified
        Component->OnPreEndOfFrameSync();
        ...
    }
} 

Problem

Users have reported crashes from ComponentsThatNeedPreEndOfFrameSync being modified during iteration. See reported callstack, which demonstrates USkeletalMeshComponent::HandleExistingParallelClothSimulation() calling FTaskGraphInterface::WaitUntilTaskCompletes(). This is problematic since that can trigger any arbitrary task graph tasks, which may modify ComponentsThatNeedPreEndOfFrameSync.

Suggestions

Avoid USkeletalMeshComponent::HandleExistingParallelClothSimulation() calling FTaskGraphInterface::WaitUntilTaskCompletes(), or make local copies of arrays in UWorld::SendAllEndOfFrameUpdates() for safe iteration. If we choose to fix it in UWorld, we should probably do the same for:

ComponentsThatNeedPreEndOfFrameSync 
ComponentsThatNeedEndOfFrameUpdate
ComponentsThatNeedEndOfFrameUpdate_OnGameThread

 

Callstack
  1. UWorld::MarkActorComponentForNeededEndOfFrameUpdate(UActorComponent * Component, bool bForceGameThread) Line 900 C++
  2. UActorComponent::MarkRenderStateDirty() Line 1851 C++
  3. [Inline Frame] FMarkRenderStateDirtyTask::DoTask(ENamedThreads::Type) Line 4264 C++
  4. TGraphTask<FMarkRenderStateDirtyTask>::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32>> & NewTasks, ENamedThreads::Type CurrentThread, bool bDeleteOnCompletion) Line 1310 C++
  5. [Inline Frame] FBaseGraphTask::Execute(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32>> & CurrentThread, ENamedThreads::Type) Line 919 C++
  6. FNamedTaskThread::ProcessTasksNamedThread(int QueueIndex, bool bAllowStall) Line 758 C++
  7. FNamedTaskThread::ProcessTasksUntilQuit(int QueueIndex) Line 649 C++
  8. [Inline Frame] FTaskGraphCompatibilityImplementation::ProcessThreadUntilRequestReturn(ENamedThreads::Type CurrentThread) Line 2069 C++
  9. FTaskGraphCompatibilityImplementation::WaitUntilTasksComplete(const TArray<TRefCountPtr<FGraphEvent>,TSizedInlineAllocator<4,32,TSizedDefaultAllocator<32>>> & Tasks, ENamedThreads::Type CurrentThreadIfKnown) Line 2123 C++
  10. FTaskGraphInterface::WaitUntilTaskCompletes(const TRefCountPtr<FGraphEvent> & Task, ENamedThreads::Type CurrentThreadIfKnown) Line 404 C++
  11. USkeletalMeshComponent::HandleExistingParallelClothSimulation() Line 2233 C++
  12. UWorld::SendAllEndOfFrameUpdates() Line 1076 C++
  13. FRendererModule::BeginRenderingViewFamilies(FCanvas * Canvas, TArrayView<FSceneViewFamily *,int> ViewFamilies) Line 4553 C++
  14. FRendererModule::BeginRenderingViewFamily(FCanvas * Canvas, FSceneViewFamily * ViewFamily) Line 4530 C++
  15. UGameViewportClient::Draw(FViewport * InViewport, FCanvas * SceneCanvas) Line 1706 C++
  16. FViewport::Draw(bool bShouldPresent) Line 1852 C++
  17. UGameEngine::RedrawViewports(bool bShouldPresent) Line 733 C++
  18. UGameEngine::Tick(float DeltaSeconds, bool bIdleMode) Line 1887 C++
  19. FEngineLoop::Tick() Line 5812 C++

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-207947 in the post.

0
Login to Vote

Unresolved
ComponentUE - Gameplay - Components
Affects Versions5.3
Target Fix5.5
CreatedFeb 23, 2024
UpdatedMar 1, 2024