fix(server/state): don't insert into the world grid map under a shared lock#4062
fix(server/state): don't insert into the world grid map under a shared lock#4062Syxless wants to merge 1 commit into
Conversation
…d lock The relevance pass in ServerGameState::Tick resolved the world grid with operator[] on m_worldGrids while holding only a shared lock. For a client whose routing bucket has no grid yet (for example when the bucket changed after UpdateWorldGrid ran for this tick), operator[] inserts into the map concurrently with other readers. The inserted entry is also a null grid, which UpdateWorldGrid then finds and bails on from that point onwards, leaving the bucket without world grid ownership until it empties out. Since grids are only created and erased in UpdateWorldGrid, which runs earlier in the same tick on the same thread, the grid for the client's routing bucket can be resolved once per client with find() instead of taking the lock and walking the map for every entity in the pass.
|
Hi, both emplace and erase of the world grid happens inside the tick. Please provide an scenario where emplace or erase of the world grid happens outside. |
|
Hello! The emplace and erase in
Even without the race, the inserted entry is a null |
Goal of this PR
While reading the relevance pass in
ServerGameState::TickI noticed it resolves the world grid differently from every other place that touchesm_worldGrids:Everything else uses
find().operator[]on astd::mapinserts when the key is missing, and here that happens while holding only the reader side of the lock, racing any concurrent reader of the map. The key can actually be missing:UpdateWorldGridcreates grids at the start of the tick, but a routing bucket change from another thread (or a player entity appearing mid tick) leaves the pass looking at a bucket that has no grid yet.The inserted entry is also a default constructed null
unique_ptr, and sinceUpdateWorldGridonly creates a grid whenfind()fails, it keeps finding the null entry and bailing on itsif (!grid)check. From then on nobody in that bucket gets world grid ownership until the bucket empties out.How is this PR achieving the goal
Resolving the grid once per client with
find()before the entity loop, like the other call sites do. This is safe because grids are only created and erased inUpdateWorldGrid, which runs earlier in the same tick on the same thread. A missing grid now just means no world grid relevance for that pass, which is what the next tick would produce anyway, instead of poisoning the bucket.This also removes a
shared_mutexacquisition and a map walk per entity per client from the pass.This PR applies to the following area(s)
Server, OneSync
Successfully tested on
Platforms: Windows
Checklist
Fixes issues