Skip to content

Fix KeyNotFoundException by adding TryGetValue checks in EntityManager#67

Open
Kyr27 wants to merge 1 commit into
themeldingwars:masterfrom
Kyr27:fix(KeyNotFoundException)
Open

Fix KeyNotFoundException by adding TryGetValue checks in EntityManager#67
Kyr27 wants to merge 1 commit into
themeldingwars:masterfrom
Kyr27:fix(KeyNotFoundException)

Conversation

@Kyr27
Copy link
Copy Markdown

@Kyr27 Kyr27 commented May 4, 2026

Summary

Fix for the KeyNotFoundException in EntityManager that was happening after around a minute of running the GameServer, but not entering the game. This error was due to using direct indexing in a few problematic areas.

List of changes:

  • Replace direct indexing with TryGetValue in OnRemovedEntity
  • Add TryGetValue guard to FlushViewChangesToScoped
  • Replace direct indexing with TryGetValue in Tick.
  • Added TryGetValue guard to ScopedPlayersByEntity

Error Details:

System.Collections.Generic.KeyNotFoundException
HResult=0x80131577
Message=The given key '2302054457216711424' was not present in the dictionary.
Source=System.Collections.Concurrent
StackTrace:
at System.Collections.Concurrent.ConcurrentDictionary`2.ThrowKeyNotFoundException(TKey key) in System.Collections.Concurrent\ConcurrentDictionary.cs:line 1124
at GameServer.Systems.EntityManager.EntityManager.FlushChanges(IEntity entity)
at GameServer.Entities.BaseAptitudeEntity.ClearEffect(EffectState state)
at GameServer.Aptitude.AbilitySystem.DoRemoveEffect(EffectState activeEffect)
at GameServer.Aptitude.AbilitySystem.ProcessTarget(IAptitudeTarget entity, UInt64 currentTime)
at GameServer.Aptitude.AbilitySystem.Tick(Double deltaTime, UInt64 currentTime, CancellationToken ct)
at GameServer.Shard.Tick(Double deltaTime, UInt64 currentTime, CancellationToken ct)
at GameServer.Shard.RunThread(CancellationToken ct)
at Shared.Udp.Utils.<>c__DisplayClass1_0.b__0(Object o)
at System.Threading.Thread.StartCallback()

…seconds of running the GameServer, but not entering the game. This error was due to using direct indexing in a few problematic areas.

List of changes:

- Replace direct indexing with TryGetValue in OnRemovedEntity
- Add TryGetValue guard to FlushViewChangesToScoped
- Replace direct indexing with TryGetValue in Tick.
- Added TryGetValue guard to ScopedPlayersByEntity
@eXpl0it3r
Copy link
Copy Markdown
Collaborator

Do you happen to know, why the index becomes invalid? Why can't the entity be found with the given id?

@Kyr27
Copy link
Copy Markdown
Author

Kyr27 commented May 4, 2026

Do you happen to know, why the index becomes invalid? Why can't the entity be found with the given id?

Race conditions, concurrent dictionaries don't make you fully immune to them, so you still have to double check everything before touching any of it. There are probably other ways to fix it too, but this one is the simplest probably.

For example, an entity could be removed from ScopedPlayersByEntity by the time we get it from Shard.Entities and when we try to access the dictionary. Adding a simple check like TryGetValue is the easiest way to prevent a crash.

I added the checks wherever i felt like it could happen, it may be overkill, but otherwise it could become a problem later on, you never know with race conditions.

@Kyr27
Copy link
Copy Markdown
Author

Kyr27 commented May 4, 2026

Do you happen to know, why the index becomes invalid? Why can't the entity be found with the given id?

The index can become invalid because entities in ScopedPlayersByEntity can be removed at any time, but others may still try to access it before the removal is fully completed.

@Xsear
Copy link
Copy Markdown
Contributor

Xsear commented May 4, 2026

Hi, thanks for your contribution!

I set the server to LoadZoneEntities in Zone 448, but I couldn't reproduce the issue after running the server over 5 minutes, several times. Do you have any more details for repro steps? Did you log into the server while running?

Looking at the stacktrace, I suspect it it could be caused by AbilitySystem.ProcessTarget potentially processing an entity with more than one active effect, where one effect executes DestroyAbilityObjectCommand and removes the entity itself. Then when the next effect on the entity is processed, the entity has already been removed from the manager.

Regarding the changes, I think it's a good idea to add guards to FlushViewChangesToScoped and SendToScoped. I'm not sure it's really needed for Tick and OnRemovedEntity but we could keep that for good measure. One thing I don't see is why it's also being applied to the usage of NetChannels though. That dict should be immutable.

@Kyr27
Copy link
Copy Markdown
Author

Kyr27 commented May 4, 2026

Hi, thanks for your contribution!

I set the server to LoadZoneEntities in Zone 448, but I couldn't reproduce the issue after running the server over 5 minutes, several times. Do you have any more details for repro steps? Did you log into the server while running?

Looking at the stacktrace, I suspect it it could be caused by AbilitySystem.ProcessTarget potentially processing an entity with more than one active effect, where one effect executes DestroyAbilityObjectCommand and removes the entity itself. Then when the next effect on the entity is processed, the entity has already been removed from the manager.

Regarding the changes, I think it's a good idea to add guards to FlushViewChangesToScoped and SendToScoped. I'm not sure it's really needed for Tick and OnRemovedEntity but we could keep that for good measure. One thing I don't see is why it's also being applied to the usage of NetChannels though. That dict should be immutable.

There aren't really any special steps to reproduce, everything was done as instructed:

  1. Clone repo with submodules using git clone --recurse-submodules https://github.com/themeldingwars/PIN.git
  2. Build the solution
  3. Configure Startup Projects in Visual Studio to start WebHostManager, GameServer, MatrixServer
  4. Start, knowing that it will throw a DirectoryNotFoundException, but need it to generate GameServer.dll.config
  5. Change StaticDBPath in GameServer.dll.config' to point to my Firefall Installation:
    <add key="StaticDBPath" value="H:\Steam\steamapps\common\Firefall\system\db\clientdb.sd2"/>
  6. Edit firefall.ini
  7. Replace FirefallClient.exe with the patched version from Release 1.2.0
  8. Start Multiple Startup Projects via Visual Studio
  9. Run for around 55 seconds to 1:20 min before crashing with a KeyNotFoundException

The above was done on Windows 10 with Extended Security Updates. version 22H2 OS Build 19045.7184

At some point i tried getting into the game before it crashes, however the GameServer would always crash before i finished loading.

The only real change i did at first was change the .NET version to .NET 10 of AeroMessages so that i didn't have to wait for VS to install .NET 6.0. That threw an exception so i downgraded to .NET 8.0 (i really didn't want to install another .NET version...), that threw an exception too, so i then downloaded .NET 6.0, that too, however, threw the same exception.

I also tried running the 1.2.0 release, works as expected without any issues.

About the changes you are probably right, i didn't pay that much attention and just wanted to get into the game, if NetChannels is immutable it doesn't need it. This was kind of a dirty fix honestly, it's probably better to add proper locks. I think this implementation is too laggy or something else is also happening, now i noticed that spawning anything causes a short connection problem to appear, although that may have been partially due to running Visual Studio Debugger in the background.

Should i make a new issue and move all the relevant steps and logs over there?
Here are the console logs from a few minutes of playing after adding guards:
GameServer.txt
MatrixServer.txt
WebHostManager.txt

@Kyr27
Copy link
Copy Markdown
Author

Kyr27 commented May 4, 2026

Here are the logs without adding guards, crashes after 1:08 min:
GameServer.txt
MatrixServer.txt
WebHostManager.txt

Visual Studio Debugger catched:
Exception thrown: 'System.Collections.Generic.KeyNotFoundException' in System.Collections.Concurrent.dll
An unhandled exception of type 'System.Collections.Generic.KeyNotFoundException' occurred in System.Collections.Concurrent.dll
The given key '2302699196232350464' was not present in the dictionary.

@Kyr27
Copy link
Copy Markdown
Author

Kyr27 commented May 4, 2026

I moved all the details about the bug into a new issue. #68 (comment)

I also tested this fix again and i think it's too laggy or there is something else also going on, spawning any kind of entity causes short connection problems. It may be better to consider alternative solutions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants