Quick answer: Store the TSharedPtr<FStreamableHandle> in your class. Call Handle->CancelHandle() to cancel a load and release its reference. Without this, partially-loaded assets stay in memory.
A level loader uses StreamableManager to async-load level assets. If the player backs out before load completes, you discard the result — but Memreport shows the partially-loaded assets persist long after. Each level retry adds to the leak.
What FStreamableHandle Tracks
StreamableManager.RequestAsyncLoad returns a TSharedPtr<FStreamableHandle>. The handle:
- Tracks load progress.
- References every asset in its load list (preventing GC).
- Calls your completion delegate when finished.
If you discard the TSharedPtr while load is in progress, the load continues (the StreamableManager keeps a reference internally) and the assets stay loaded indefinitely.
The Fix
class ALevelLoader : public AActor
{
TSharedPtr<FStreamableHandle> CurrentLoad;
public:
void StartLoad() {
UAssetManager& AM = UAssetManager::Get();
CurrentLoad = AM.LoadAssetList(AssetPaths, FStreamableDelegate::CreateUObject(this, &ThisClass::OnLoaded));
}
void AbortLoad() {
if (CurrentLoad.IsValid()) {
CurrentLoad->CancelHandle();
CurrentLoad.Reset();
}
}
};
CancelHandle stops the load and drops the handle’s asset references. Reset on the TSharedPtr releases the handle itself. Next GC pass, the partially-loaded assets become eligible for collection.
Verify with Memreport
Console: memreport -full. Search the output for your asset class names. After AbortLoad + GC tick, the count should drop. If unchanged, you have another reference holding them — check soft references, ObjectIterator usage, etc.
Force GC for testing: obj gc in console.
Auto-Cancel Pattern
For per-actor loads that should cancel on actor destruction:
virtual void EndPlay(const EEndPlayReason::Type Reason) override
{
if (CurrentLoad.IsValid()) {
CurrentLoad->CancelHandle();
}
Super::EndPlay(Reason);
}
Actor leaves the world → pending loads cancel. Prevents callbacks firing on a destroyed actor and frees memory.
Don’t Cancel Completed Handles
If the load already completed, calling CancelHandle is a no-op but the handle still holds references. To free completed loads, use ReleaseHandle instead.
Verifying
Trigger a load, immediately AbortLoad. Run obj gc and memreport. The assets should not appear. Run a stress test loading and aborting 100 times in a loop — memory should stay flat.
“Hold the handle; cancel the handle. Discarding the TSharedPtr without canceling leaves assets in memory.”
Every async load should have a paired cancel path. Treat partially-loaded assets as resources that need explicit cleanup.