Steps to Reproduce
We’ve been encountering a hard to reproduce crash in the package map client with this specific check being triggered:
checkf(!It.Value().Object.IsValid() || NetGUIDLookup.FindRef(It.Value().Object) == It.Key() || It.Value().ReadOnlyTimestamp != 0, TEXT("Failed to validate ObjectLookup map in UPackageMap. Object '%s' was not in the NetGUIDLookup map with with value '%s'." ), *It.Value().Object.Get()->GetPathName(), *It.Key().ToString());
The reason this check was being triggered was tricky to track down, but I finally managed to create a repro case.
In our setup, we have a client connecting to a listen server. We do allow the client to remap netguids. The client can change weapons by sending an RPC with which asset he wants to change to. If they together perform these steps:
1. Host and client are both joined into a game 2. Host switches to a weapon with a certain BP class (let's pretend it's a pistol), and switches off of that weapon to any other weapon (this is make sure package is loaded) 3. Client switches to the same weapon with the same BP class and switches off that weapon as well 4. The host then runs garbage collection forcibly 5. The host switches to pistol again, (loading the package again since it was garbage collected after nothing referenced it) 6. The client switches to the pistol as well
When these steps are done, the package map on the host ends up running through this code in PackageMapClient:
`if (bIsNotReadOnlyOrAllowRemap || !NetGUIDLookup.Contains(Object))
{
if (CacheObjectPtr->ReadOnlyTimestamp > 0)
{
UE_LOG( LogNetPackageMap, Warning, TEXT( “GetObjectFromNetGUID: Attempt to reassign read-only guid. FullNetGUIDPath: %s” ), *FullNetGUIDPath( NetGUID ) );
if (bAllowClientRemap)
{
CacheObjectPtr->ReadOnlyTimestamp = 0;
}
}
NetGUIDLookup.Add( Object, NetGUID );`That last step being the one where it stomps the previous entry into the NetGUIDLookup which actually had a higher net guid.
Later when the host performs a seamless travel, inside of void FNetGUIDCache::CleanReferences()
else if (FoundGuid < Guid) { ObjectLookup[FoundGuid].ReadOnlyTimestamp = Time; FoundGuid = Guid; }
this code will trigger. The object is in the ObjectLookup twice, and it will mark the ‘older’ guid as expiring but keep the new one. Since the NetGUIDLookup entry was stomped with the older one, it will crash when it hits the check in the loop here:
`for (auto It = ObjectLookup.CreateIterator(); It; ++It)
{
check(!It.Key().IsDefault());
check(It.Key().IsStatic() != It.Key().IsDynamic());
checkf(!It.Value().Object.IsValid() || NetGUIDLookup.FindRef(It.Value().Object) == It.Key() || It.Value().ReadOnlyTimestamp != 0, TEXT("Failed to validate ObjectLookup map in UPackageMap. Object '%s' was not in the NetGUIDLookup map with with value '%s'." ), *It.Value().Object.Get()->GetPathName(), *It.Key().ToString());
}`