Crash: CastChecked<UPCGComponent>(nullptr) in FPCGGridLinkageElement (GetLocalSource) when a partitioned runtime-gen component's Original is torn down mid-execution

CastChecked<UPCGComponent>(nullptr) crashes inside the PCG grid-linkage element when a partitioned, runtime-generated component executes a cross-grid linkage while its Original component is torn down concurrently. The first fetch of the Original is ensure-guarded; a second, unguarded re-fetch inside GetLocalSource() uses CastChecked and dies when the Original has become null in between. (Full symbolized call stack is in Steps to Reproduce / attached log.)

FATAL ERROR

[File: …\CoreUObject\Private\Templates\Casts.cpp] [Line: 10] Cast of nullptr to PCGComponent failed

Crash frame: FPCGComponentExecutionState::GetLocalSource() (PCGComponentExecutionState.cpp:198), called from PCGGraphExecutor::ExecuteGridLinkage() (PCGGraphExecutor.cpp:3606), on a task worker thread.

ROOT CAUSE (line numbers are 5.8)

In PCGGraphExecutor.cpp, the “retrieve” branch of ExecuteGridLinkage:

3595 OriginalSource = ExecutionState.GetOriginalSource();

3596 if (!ensure(OriginalSource)) return false; // FIRST fetch - guarded

3606 ExecSourceWithData = ExecutionState.GetLocalSource(…); // re-fetches Original, UNGUARDED

3609 if (!ExecSourceWithData) return true; // null LOCAL source handled gracefully

GetLocalSource (PCGComponentExecutionState.cpp:198) then does:

CastChecked<UPCGComponent>(GetOriginalSource()) // CastChecked(nullptr)

GetOriginalSource() -> Component->GetOriginalComponent(), which for a local component resolves through the partition actor (PCGPartitionActor.cpp:452):

const TSoftObjectPtr<UPCGComponent>* Orig = LocalToOriginal.Find(LocalComponent);

return Orig ? Orig->Get() : nullptr; // null if entry removed OR soft-ptr target destroyed/unloaded

So the Original resolves to null when, between line 3596 and line 198, the Original component is destroyed / unregistered / removed from LocalToOriginal (or its TSoftObjectPtr target is GC’d/unloaded). This coincides with the runtime-gen scheduler’s continuous teardown (“aborted after cleaning up N/83 components”) on another thread while the linkage task runs on a worker thread.

The asymmetry is the bug: the first Original fetch is ensure-guarded (3596) and a null LOCAL source is handled gracefully (3609), but the second Original fetch (line 198) is an unguarded CastChecked, reachable with a null Original whenever teardown races linkage execution.

EVIDENCE

The same session first logged the corresponding handled ensure (“Ensure condition failed: OriginalSource [PCGGraphExecutor.cpp] [Line: 3596]”, worker thread), then ran ~12 min before the fatal CastChecked at line 198. Both occur on worker threads during heavy FPCGRuntimeGenScheduler cleanup, right after the player crossed a chunk boundary.

SUGGESTED FIX

1) Make line 198 tolerant of a null Original, mirroring the graceful null-local-source handling at 3609 - reuse the already-validated OriginalSource from 3595, or replace CastChecked with Cast + early-out instead of asserting.

2) Cancel/drain in-flight grid-linkage tasks for a local component before its Original is removed from LocalToOriginal or destroyed during runtime-gen cleanup.

WORKAROUND

pcg.GraphMultithreading 0 (linkage runs on game thread, removing the cross-thread window); reducing runtime-gen churn (larger frame time budget / smaller generation radii) also narrows it.

Full logs and a minidump available on request.

[Attachment Removed]

Steps to Reproduce
Repro is timing-dependent (a threading/lifecycle race), so an isolated deterministic repro is hard, but the following reliably surfaces it for us:

1. A World-Partitioned level using PCG runtime generation (FPCGRuntimeGenScheduler), with pcg.GraphMultithreading enabled (default).

2. A partitioned PCG graph that uses hierarchical generation with cross-grid edges — i.e. one or more “Change Grid Size” nodes that read data produced on a coarser grid into a finer grid (in our case grids from 512 down to 16/32/64/128). Each such edge compiles to an FPCGGridLinkageElement.

3. Drive generation load high enough that the runtime-gen scheduler is continuously over budget. In our logs it prints, on essentially every tick:

“FPCGRuntimeGenScheduler: Time budget exceeded, aborted after cleaning up N / 83 components”

so partition actors / local components (and their Originals) are being created and destroyed continuously.

4. Move the player rapidly across chunk/cell boundaries so streaming continuously loads and unloads partitioned content while grid-linkage tasks are in flight on worker threads.

Under this churn, a grid-linkage task runs on a task worker thread while the Original component of the executing local/partition component is being removed/destroyed on another thread, and the crash fires. Before the fatal crash we also observed the corresponding handled ensure (same call site, line 3596) fire once, ~12 minutes earlier in the same session.

[Attachment Removed]

Hello Maximilian, thanks for the report! I agree with your assessment and suggested fix, I’m putting it through review with the team and should have it submitted in the near future.

Cheers,

Wyatt

[Attachment Removed]

Hello, we’ve submitted the fix - https://github.com/EpicGames/UnrealEngine/commit/248351a3486c2ad540a7b648fc8454f5b722e212

[Attachment Removed]