Crash when freeing referenced imports during async package loading [with potential fix]

We’ve been having rare crashes (maybe 1/20) upon opening our game-play level. It occurs either inside the destructor of FAsyncPackage or inside FAsyncPackage::FreeReferencedImports. I’ve managed to reliably reproduce the crash and I’ve noticed that these two code paths can happen simultaneously on the game thread and the async loading thread.

Inside FAsyncPackage::FreeReferencedImports():

for (int32 ReferenceIndex = 0; ReferenceIndex < ReferencedImports.Num(); ++ReferenceIndex)
{
	FAsyncPackage& Ref = *ReferencedImports[ReferenceIndex];
		
	Ref.DependencyRefCount.Decrement();
	UE_LOG(LogStreaming, Verbose, TEXT("FAsyncPackage::FreeReferencedImports for %s: Releasing %s (%d)"), *Desc.NameToLoad.ToString(), *Ref.GetPackageName().ToString(), Ref.GetDependencyRefCount());
		
	check(Ref.DependencyRefCount.GetValue() >= 0);
}
ReferencedImports.Empty();

Inside FAsyncLoadingThread::ProcessLoadedPackages:

// Delete packages we're done processing and are no longer dependencies of anything else
for (int32 PackageIndex = 0; PackageIndex < PackagesToDelete.Num(); ++PackageIndex)
{
	FAsyncPackage* Package = PackagesToDelete[PackageIndex];
	if (Package->GetDependencyRefCount() == 0 && !Package->IsBeingProcessedRecursively())
	{
		PackagesToDelete.RemoveAtSwap(PackageIndex--);
		delete Package;
		if (IsTimeLimitExceeded(TickStartTime, bUseTimeLimit, TimeLimit, TEXT("ProcessLoadedPackages PackagesToDelete")))
		{
			Result = EAsyncPackageState::TimeOut;
			break;
		}
	}
}

What happens is that the package reference count is decreased on the async loading thread, then the game thread is deleting the package because it detects the reference count is 0, and then back in the async thread it’s checking the reference count of a deleted objects, which causes the crash. I’m guessing the same thing is happening in reverse, that modifying the reference count as the package is being deleted would also cause problems.

As far as I can tell this code hasn’t changed in more recent Unreal versions, so it is unlikely to have been fixed. I’m testing a fix I’ve made which wraps these two code blocks in a critical section, and so far it seems to have fixed the problem.

I’m encountering the same problem in UE 4.17
Could you post the code that fixes this please? Thank you!