[UE 5.6.1 – ue5-main] Crash on level travel: FVirtualShadowMapClipmap does a game-world line trace from a render task, racing UWorld teardown (Chaos lock leaked → !IsLocked() assert)

Summary

With an orthographic camera + Virtual Shadow Maps and a view with no ViewTarget, FVirtualShadowMapClipmap::FVirtualShadowMapClipmap() calls UWorld::LineTraceSingleByObjectType() from a renderer task (FGatherShadowPrimitivesPrepareTask), gated by r.Ortho.VSM.RayCastViewOrigin (default true).

This trace is not synchronized with the game thread. During map travel, the game thread destroys the old UWorld / FChaosScene (UEngine::LoadMap → TrimMemory → GC → UWorld::FinishDestroy) while the render task can still be tracing against it.

In our crash, the render worker acquired the Chaos solver’s external data read lock inside the scene query, faulted, and stayed parked in the crash handler while still holding the lock. The game thread then destroyed the solver and hit:

Assertion failed: !IsLocked() [WindowsPlatformMutex.h:77] “Destroying a lock that is still held!”

The code is unchanged in 5.7, 5.8 and ue5-main (VirtualShadowMapClipmap.cpp line 268 in 5.6/5.7, line 283 in 5.8/ue5-main), and the CVar still defaults to true.

What Type of Bug are you experiencing?

Rendering (Graphics / Niagara)

Steps to Reproduce

  1. Use an orthographic camera with no ViewTarget (CameraViewMatrices.GetCameraToViewTarget() is zero), e.g. a standalone CameraActor.
  2. Enable Virtual Shadow Maps (r.Shadow.Virtual.Enable=1) with a movable directional light.
  3. Keep defaults r.Ortho.VSM.ProjectViewOrigin=1 and r.Ortho.VSM.RayCastViewOrigin=1.
  4. Have a level with active physics (simulating bodies) and repeatedly travel out of it with OpenLevel (packaged Development build, Win64/D3D12).
  5. Loop for a long session. Timing dependent: we hit it about once per ~50 travels (16th Round → Outro transition, ~99 min of continuous cycling).

Expected Result

Level travel completes normally. The renderer should never run physics scene queries against a game UWorld from a render task, or should be safely synchronized with world/physics scene teardown.

Observed Result

Intermittent crash during LoadMap/GC: a render worker is stuck in the crash handler inside UWorld::LineTraceSingleByObjectType() (called from FVirtualShadowMapClipmap ctor) while holding the Chaos external data read lock, and the game thread asserts !IsLocked() in FWindowsSharedMutex::~FWindowsSharedMutex() while destroying FPhysicsSolverBase.

Affects Versions

5.8
5.7
5.6

Platform(s)

Windows

For crash reports, include your callstack

=== Game thread ===
FWindowsSharedMutex::~FWindowsSharedMutex() WindowsPlatformMutex.h:77
Chaos::FPhysicsRwLock::~FPhysicsRwLock() Threading.h:762
TDefaultDeleteChaos::FPhysicsRwLock::operator()
TUniquePtrChaos::FPhysicsRwLock::~TUniquePtr()
Chaos::FPhysicsSolverBase::~FPhysicsSolverBase() Line 304
FChaosSolversModule::DestroySolver() Line 271
FChaosScene::~FChaosScene() Line 117
UWorld::ReleasePhysicsScene() Line 1435
UWorld::FinishDestroy() Line 1471
UObject::ConditionalFinishDestroy()
IncrementalDestroyGarbage / IncrementalPurgeGarbage
CollectGarbage()
UEngine::TrimMemory() Line 16240
UEngine::LoadMap() Line 15839
UEngine::Browse()
UEngine::TickWorldTravel()
UGameEngine::Tick()
FEngineLoop::Tick()

=== Render worker (Background Worker, holds the Chaos read lock) ===
FCrashReportingThread::WaitUntilCrashIsHandled
ReportCrash

TArray<…>::~TArray()
FGenericChaosSQAccelerator::Raycast()
LowLevelRaycast()
FSQTraits<…>::SceneTrace()
TSceneCastCommonImpWithRetryRequest<…>
TSceneCastCommonImp<…>
TraceCommonImp / TraceCommon / TSceneCastCommon
FGenericPhysicsInterface::RaycastSingle()
UWorld::LineTraceSingleByObjectType()
FVirtualShadowMapClipmap::FVirtualShadowMapClipmap() VirtualShadowMapClipmap.cpp:268
FShadowSceneRenderer::AddDirectionalLight…
FSceneRenderer::AddViewDependentWholeSceneShadowsForView()
FSceneRenderer::CreateDynamicShadows()
FGatherShadowPrimitivesPrepareTask::DoTask()
TGraphTask<…>::ExecuteTask()
LowLevelTasks::FScheduler::ExecuteTask()

Additional Notes

Root cause: VirtualShadowMapClipmap.cpp, inside the (bIsOrthographicCamera && CVarOrthoVSMProjectViewOrigin) block, calls DependentView->Family->Scene->GetWorld()->LineTraceSingleByObjectType(…) from the render pipeline. Physics scene queries on a game UWorld are not safe from render tasks: nothing prevents the world / physics scene from being torn down concurrently, and the render thread runs up to a frame behind the game thread, so the LoadMap → GC window is exposed. No game code or plugin in our project takes the Chaos scene lock directly; all other threads were idle at crash time.

Suggested fix:

  • Remove the render-side trace; compute the view-origin estimate on the game thread (view setup) and pass it via FSceneView / view matrices, like CameraToViewTarget.
  • At minimum skip the trace when the world is tearing down (not sufficient on its own, the access is still unsynchronized).
  • Consider defaulting r.Ortho.VSM.RayCastViewOrigin to false until fixed.

Workaround (confirmed root cause, CVar disables the trace):
[SystemSettings]
r.Ortho.VSM.RayCastViewOrigin=0

Minidump / crash context / log available on request.