Rare crash caused by EXCEPTION_ACCESS_VIOLATION in Contact Modifications when accessing Actors from Chaos::FGeometryParticleHandle

Hello,

we are recently experiencing some crashes when using Chaos::TSimCallbackObject for Contact Modification in UE 5.6, probably caused by a thread race condition, though we are not 100% sure about that.

Our current use-case is modifying collisions to get the correct desired interaction between specific types of Actors in our project; we use it to make colliding particles extremely light or heavy depending on which Actor is colliding.

This system has been working fine so far, but we have been rarely experiencing some crashes we can’t reproduce reliably.

We are aware a liability of our architecture is the fact we access AActor instances in order to check their type and decide how we should handle it, eventually reading some data from the Actor to modify the collision accordingly. For instance, for TypeA we may want to make the collision particularly heavy, for TypeB we need to read some data to know if we should ignore the collision or modify it, …

We access the Actor from the Physics Thread through the BodyInstance (consider that returning nullptr here is not a problem for our implementation):

AActor* FGameAsyncCallback::GetParticleOwningActor(const Chaos::FGeometryParticleHandle* Particle) const
{
  FBodyInstance* BodyInstance = FChaosUserData::Get<FBodyInstance>(Particle->GTGeometryParticle()->UserData());
  
  return (BodyInstance && BodyInstance->OwnerComponent.IsValid()) ? BodyInstance->OwnerComponent->GetOwner() : nullptr;
}

To my understanding, we most probably are facing a race condition between the Game Thread and the Physics Thread. Only part of our Actors are actually destroyed, while we apply a pooling system to most of our other Actors.

To circumvent this problem, we tried applying a ‘ghosting’ solution on the Actors we need to destroy:

1. When we need to destroy them, we first call:

Component->SetSimulatePhysics(false);
Component->SetCollisionEnabled(ECollisionEnabled::NoCollision);

on each UPrimitiveComponent;

2. then we call:

SetLifeSpan(Delay);to destroy them shortly after, with a delay big enough that they have been excluded from Contact Modifications.

The crashes have not improved much this way. From our research it’s unclear whether setting ECollisionEnabled::NoCollision may be causing the crash as well, destroying the physics state before Contact Modification is able to complete its cycle. Our next tentative solution is using the same approach, but just disabling the collision response to all Collision Channels instead of setting ECollisionEnabled::NoCollision.

We are aware this is most probably not the best practice and using the Chaos::TSimCallbackObject Input would probably be better, but, at the current time, changing the architecture drastically is not really feasible for us. In order to still be able to access the Actor instance without causing crashes, is there any other workaround we might try? What would the best practices be for such usage of Contact Modifications?

The most important data we need to pass down to Contact modifications are:

- a Particle’s owner Actor’s type (or another way to identify that)

- payload data that depend on the Actor’s type

Thank you for your time,

Michelangelo

[Attachment Removed]

Sorry, unfortunately it seems the callstacks I added to the post got lost in the process. Pasting them here instead.

Callstack #1: crashed when BodyInstance->OwnerComponent->GetOwner() was called.

Callstack #2: crashed when BodyInstance->OwnerComponent.IsValid() was called.

Game_Win64_Test!UActorComponent::GetWorld_Uncached() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\Components\ActorComponent.cpp:1010]
Game_Win64_Test!UChaosGameplayEventDispatcher::HandleCollisionEvents() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\ChaosSolverEngine\Private\Chaos\ChaosGameplayEventDispatcher.cpp:406]
Game_Win64_Test!Chaos::TEventContainer<Chaos::FCollisionEventData>::DispatchConsumerData() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Public\EventManager.h:362]
Game_Win64_Test!Chaos::FEventManager::DispatchEvents() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\EventManager.cpp:104]
Game_Win64_Test!FChaosScene::EndFrame() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\PhysicsCore\Private\ChaosScene.cpp:575]
Game_Win64_Test!FEndPhysicsTickFunction::ExecuteTick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\PhysicsEngine\PhysLevel.cpp:289]
Game_Win64_Test!TGraphTask<FTickFunctionTask>::ExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Async\TaskGraphInterfaces.h:706]
Game_Win64_Test!UE::Tasks::Private::FTaskBase::TryExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Tasks\TaskPrivate.h:528]
Game_Win64_Test!FNamedTaskThread::ProcessTasksUntilIdle() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:678]
Game_Win64_Test!FTaskGraphCompatibilityImplementation::ProcessUntilTasksComplete() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:1592]
Game_Win64_Test!FTickTaskSequencer::ReleaseTickGroup() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\TickTaskManager.cpp:986]
Game_Win64_Test!FTickTaskManager::RunTickGroup() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\TickTaskManager.cpp:2079]
Game_Win64_Test!UWorld::Tick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\LevelTick.cpp:1533]
Game_Win64_Test!UGameEngine::Tick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\GameEngine.cpp:1878]
Game_Win64_Test!FEngineLoop::Tick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp:5625]
Game_Win64_Test!GuardedMain() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Launch.cpp:187]
Game_Win64_Test!GuardedMainWrapper() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:128]
Game_Win64_Test!LaunchWindowsStartup() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:282]
Game_Win64_Test!WinMain() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:340]
Game_Win64_Test!__scrt_common_main_seh() [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
kernel32
ntdll
Game_Win64_Test!FWeakObjectPtr::IsValid() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\CoreUObject\Private\UObject\WeakObjectPtr.cpp:60]
Game_Win64_Test!FGameAsyncCallback::OnContactModification_Internal() [C:\unrealprojects\Game\Source\Game\Private\Game\WorldBuilding\T4ReactiveActorAsyncCallback.cpp:51]
Game_Win64_Test!Chaos::FPBDCollisionConstraints::ApplyCollisionModifier() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\Chaos\PBDCollisionConstraints.cpp:630]
Game_Win64_Test!Chaos::FPBDRigidsEvolutionGBF::AdvanceOneTimeStepImpl() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\Chaos\PBDRigidsEvolutionGBF.cpp:635]
Game_Win64_Test!Chaos::FPBDRigidsEvolutionGBF::AdvanceOneTimeStep() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\Chaos\PBDRigidsEvolutionGBF.cpp:377]
Game_Win64_Test!Chaos::AdvanceOneTimeStepTask::DoWork() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\PBDRigidsSolver.cpp:555]
Game_Win64_Test!Chaos::FPBDRigidsSolver::AdvanceSolverBy() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\PBDRigidsSolver.cpp:1371]
Game_Win64_Test!Chaos::FPhysicsSolverAdvanceTask::AdvanceSolver() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\Chaos\Framework\PhysicsSolverBase.cpp:181]
Game_Win64_Test!TGraphTask<Chaos::FPhysicsSolverAdvanceTask>::ExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Async\TaskGraphInterfaces.h:706]
Game_Win64_Test!UE::Tasks::Private::FTaskBase::TryExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Tasks\TaskPrivate.h:528]
Game_Win64_Test!LowLevelTasks::TTaskDelegate<LowLevelTasks::FTask * __cdecl(bool),48>::TTaskDelegateImpl<`LowLevelTasks::FTask::Init<`UE::Tasks::Private::FTaskBase::Init'::`2'::<lambda_1> >'::`5'::<lambda_1>,0>::CallAndMove() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Async\Fundamental\TaskDelegate.h:171]
Game_Win64_Test!LowLevelTasks::FTask::ExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Async\Fundamental\Task.h:627]
Game_Win64_Test!LowLevelTasks::FScheduler::ExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Async\Fundamental\Scheduler.cpp:364]
Game_Win64_Test!LowLevelTasks::FScheduler::WorkerLoop() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Async\Fundamental\Scheduler.cpp:724]
Game_Win64_Test!`LowLevelTasks::FScheduler::CreateWorker'::`2'::<lambda_1>::operator()() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Async\Fundamental\Scheduler.cpp:188]
Game_Win64_Test!FThreadImpl::Run() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\HAL\Thread.cpp:68]
Game_Win64_Test!FRunnableThreadWin::Run() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Windows\WindowsRunnableThread.cpp:159]

[Attachment Removed]

Callstack #3: from a Chaos related crash on a different project, it seems to crash when similarly calling GetOwner() but it seems to be fully contained in UE code. We are not sure whether this is being called on the Game Thread or Physics Thread, but as it happened recently and seems a similar case, we thought of including it here.

Game_Win64_Test!UActorComponent::GetWorld_Uncached() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\Components\ActorComponent.cpp:1010]
Game_Win64_Test!UChaosGameplayEventDispatcher::HandleCollisionEvents() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\ChaosSolverEngine\Private\Chaos\ChaosGameplayEventDispatcher.cpp:406]
Game_Win64_Test!Chaos::TEventContainer<Chaos::FCollisionEventData>::DispatchConsumerData() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Public\EventManager.h:362]
Game_Win64_Test!Chaos::FEventManager::DispatchEvents() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Experimental\Chaos\Private\EventManager.cpp:104]
Game_Win64_Test!FChaosScene::EndFrame() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\PhysicsCore\Private\ChaosScene.cpp:575]
Game_Win64_Test!FEndPhysicsTickFunction::ExecuteTick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\PhysicsEngine\PhysLevel.cpp:289]
Game_Win64_Test!TGraphTask<FTickFunctionTask>::ExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Async\TaskGraphInterfaces.h:706]
Game_Win64_Test!UE::Tasks::Private::FTaskBase::TryExecuteTask() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Public\Tasks\TaskPrivate.h:528]
Game_Win64_Test!FNamedTaskThread::ProcessTasksUntilIdle() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:678]
Game_Win64_Test!FTaskGraphCompatibilityImplementation::ProcessUntilTasksComplete() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp:1592]
Game_Win64_Test!FTickTaskSequencer::ReleaseTickGroup() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\TickTaskManager.cpp:986]
Game_Win64_Test!FTickTaskManager::RunTickGroup() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\TickTaskManager.cpp:2079]
Game_Win64_Test!UWorld::Tick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\LevelTick.cpp:1533]
Game_Win64_Test!UGameEngine::Tick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Engine\Private\GameEngine.cpp:1878]
Game_Win64_Test!FEngineLoop::Tick() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp:5625]
Game_Win64_Test!GuardedMain() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Launch.cpp:187]
Game_Win64_Test!GuardedMainWrapper() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:128]
Game_Win64_Test!LaunchWindowsStartup() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:282]
Game_Win64_Test!WinMain() [C:\unrealprojects\UE4EngineTemplate\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:340]
Game_Win64_Test!__scrt_common_main_seh() [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
kernel32
ntdll

[Attachment Removed]

Out of interest, is your component in question a multi-body primitive such as an ISM, Skeletal Mesh or Geometry Collection, and are you using Async Physics?

There is a use-after-free issue with multi-body primitives that is still present in 5.7, and is caused by body instance memory being freed by the game thread while the physics thread still works on them or has queued events. Because it’s a use-after-free, it’s difficult to track normally- but you can usually catch it immediatelly with the stomp allocator.

My thread for that issue is here, but they seem related. Body Instances have no reference counting but are passed to async lambas unsafely. This is only an issue for multi-body primitives where the body instance memory is not directly embedded in the component. Changing the collision to NoCollision is enough to destroy them. There is no known fix/alteration as of yet it seems:

[Content removed]

[Attachment Removed]

Hi Michelangelo, and thank you for the question. Apologies this has taken a while to respond to. As James mentiones there is an issue we have been chasing on the Geometry Collection side, which is essentially an use-after-free issue (which we’ve had a lead on recently and may have an answer after the company break). However that one is specific to Geometry Collections and Cluster Unions.

My initial impression is that this may be different if you aren’t using either of those. Have you tried to repro this in 5.7 out of interest?

Best

Geoff Stacey

Developer Relations

EPIC Games

[Attachment Removed]