Skip to content

Fix two race conditions - #5156

Open
ZacSharp wants to merge 1 commit into
cabaletta:1.21.4from
ZacSharp:pr/1.21.4/elytra/threadSafetyFix2
Open

ZacSharp wants to merge 1 commit into
cabaletta:1.21.4from
ZacSharp:pr/1.21.4/elytra/threadSafetyFix2

Conversation

@ZacSharp

Copy link
Copy Markdown
Collaborator

Making the boi cache thread safe the same way as the bsi cache is not possible, because the shared empty/solid nether pathfinder chunks are reused for multiple positions. Unlike the change proposed by @0Mattias this does not use a volatile field.

An alternative to this is to use separate caches for multiple threads, but that's more complicated and unless benchmarks show it's significantly faster I wouldn't go for it. See #5155 for that approach.


As a little drive-by fix this also makes the bsi used by ElytraBehavior thread safe, which is required because the solver thread uses it. Idk why it doesn't use baritone.bsi.
There is also some optimization potential by making the solver claim the bsi caches, but that needs a bit more work because unlike the regular pathfinder the pitch solver can run two times in parallel.

Fixes #5136

1. `ElytraBehavior` needs a thread safe `BlockStateInterface` for the solver
2. Make `BlockStateOctreeInterface` thread safe
0Mattias pushed a commit to 0Mattias/cheesecake that referenced this pull request Oct 26, 2026
ZacSharp's 415acfcf, ported as it is: the cached (chunk, position) pair is a
record behind a plain field, read once into a local and compared with the xor
trick, in place of the fork's record behind a volatile field; and the elytra
behavior's BlockStateInterface is the thread-safe copy, since the solver
thread reads it for the lava case. The fork's invalidate() stays as the way
the writer clears the field. Pushed so that CI's in-world test flies the four
elytra stages with the PR's code, which the benchmark alone does not show.

The chunk-cache workflow no longer triggers on this branch, so this push
runs CI only.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyYJgBP5vLVRjrcXVvTizn
0Mattias pushed a commit to 0Mattias/cheesecake that referenced this pull request Oct 26, 2026
Reverts the #5155 port so the branch head carries #5156's code once more,
now that CI prints what the client log holds; the previous #5156 flight ran
before that report existed.

Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyYJgBP5vLVRjrcXVvTizn
@0Mattias

Copy link
Copy Markdown
Contributor

Agreed, thanks for catching this, seems like volatile isn't needed: the record's fields are final, cached = null happens under the write lock, and every get0 caller holds the read lock (onTick0, tick() via ElytraProcess, the solver). Dropping it in my fork.

This is what my fork has flown since September, and I ported this exact PR onto it and ran the full in-game test twice (all stages, four elytra flights): passed both times, no exceptions from the mod (https://github.com/0Mattias/cheesecake/actions/runs/40257676667, https://github.com/0Mattias/cheesecake/actions/runs/40259193688). So it works. It's just not the fast option: replaying two fresh in-game flights through this get0 on five machines gave +4% to +34% per call vs today (slower in all 10 runs, 9 beyond the confidence intervals), a 24 byte allocation every 16 calls, and 14 to 45 ns per call when two solves overlap against 2.6 to 7.5 for a cache per solver, which on one thread is within 6% of today everywhere but the m1. Full tables on #5155.

For scale, get0 is about 5% of a solve, so the worst case is about 2% of a solve; it's likely that nobody will feel it. But if #5155 loses its WeakHashMap (see my comment there) it's both simpler and faster than this, so that's the one I'd merge. Fine with either, this is the smaller diff.

The BlockStateInterface(ctx, true) change looks right, the solver thread does read bsi for the lava case. Taking it in the fork too.

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.

BlockStateOctreeInterface can answer from the wrong chunk when two threads use it

2 participants