Skip to content

fix(net): bump equilibrium for the EBR-guarded map iteration begin - #4

Merged
Force67 merged 1 commit into
developfrom
fix/bump-equilibrium-ebr-begin
Sep 19, 2026
Merged

Force67 merged 1 commit into
developfrom
fix/bump-equilibrium-ebr-begin

Conversation

@Force67

@Force67 Force67 commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Closes the field crash observed in long-running multiplayer sessions; the container fix lives in Force67/equilibrium#17 (submodule bump only here).

The crash

Thread 13 "recreation" received signal SIGSEGV
#0 tx::network::ZPacketQueue::ProcessOutgoingPackets
   at znet/z_packet_queues.cc:314
   const i32 packet_age = static_cast<i32>(now - packet.last_send_time);

The reliable-transport retry scan range-fors awaiting_ack_packets_ every
retry interval. Under minutes of gameplay traffic (constant reliable packets +
ack churn) that traversal faulted dereferencing a near-null chain node —
confirmed in core dumps from three separate crashed sessions, with the same
signature each time.

Root cause

LockFreeOrderedHashMap::begin() walked the bucket chains looking for the
first live node before the iterator's ebr::Guard announced. The skipped
deleted-but-not-yet-swept nodes are exactly what a concurrent
collect_garbage() pass unlinks, retires and frees, so the preamble could
step into freed memory and start the traversal on a garbage node.
order_begin() had the same gap on a smaller window.

The bump

vendor/equilibrium e68c2b8 → efb03b7 (Force67/equilibrium#17): announce the
epoch before the first dereference in begin()/order_begin() and keep the
guard pin continuous into the iterator. The container's existing test suite
passes under ASan with the change, and a begin()-under-churn stress
reproducer that faults in the preamble on the parent commit no longer does.

Follow-up

Force67/equilibrium#18 documents a separate, deeper iterator-protocol flaw
(reproducible under duplicate-key insert churn, structural rather than
lifetime-related) with reproducers; it needs its own root-cause pass and does
not block this bump, which fixes the unguarded preamble the production crash
traversed.


Updated 2026-09-19: what this bump now carries

Force67/equilibrium#17 was rebased onto devel5 to clear a conflict, so its
commit changed and this pointer moved with it. The pin here was e68c2b8, which
turned out to be 25 commits behind devel5, so this is no longer a one-commit
bump: it advances vendor/equilibrium to devel5 plus the iteration fix, 26
commits. The notable ones beyond the fix:

  • 74e85ca Remove the STL and the C runtime from base
  • 363b036 / dc1a10c / 3223dd4 security bug bash 1-3
  • 52d4bea container value semantics, string search and std view interop
  • 3c336fe compile the windows sources with a mingw toolchain
  • f961dcb an Option<const char*> owns the string it is given

Verified locally against this exact pointer, in the configuration that consumes
equilibrium (-DZNET_USE_STL=OFF):

  • the library and the samples build clean;
  • handshake_integration and mp_integration pass;
  • the stressed chaos-soak profile CI runs (drop/reorder/jitter 5/5/5, seeds 1-3)
    passes.

Two notes on merging:

  • Land fix(base): announce the EBR epoch before begin()'s first node dereference equilibrium#17 first, then re-point this at the resulting devel5
    commit. Right now it names the PR branch's head, which a submodule fetch can
    only reach while that branch exists.
  • The red checks here are not this PR. Every zetanet CI run has failed since
    2026-09-01 (including the two that were merged): the actions/checkout steps
    in all three workflows lack submodules: recursive, so no vendored dependency
    is present and the build dies on xxhash.h (which lives in vendor/lz4/lib).

@Force67
Force67 force-pushed the fix/bump-equilibrium-ebr-begin branch from ce3266b to bafeba5 Compare September 19, 2026 08:15
vendor/equilibrium e68c2b8 -> efb03b7:

LockFreeOrderedHashMap::begin() walked the bucket chains looking for the
first live node before the iterator's ebr::Guard announced -- exactly the
nodes a concurrent collect_garbage() pass sweeps, retires and frees.
Under sustained reliable-packet + ack churn the preamble could step into
freed memory and hand the retry scan a garbage start node, which is the
segfault in ZPacketQueue::ProcessOutgoingPackets (z_packet_queues.cc:314)
observed in long-running multiplayer sessions: the retry scan faults
dereferencing a near-null chain node.

order_begin() had the same load-before-announce gap on a smaller window.
The bump announces the epoch before the first dereference and keeps the
pin continuous into the iterator; the container's existing test suite
passes under ASan with the change.
@Force67
Force67 force-pushed the fix/bump-equilibrium-ebr-begin branch from bafeba5 to 9aeefb5 Compare September 19, 2026 08:35
@Force67
Force67 merged commit 4822d2a into develop Sep 19, 2026
0 of 4 checks passed
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.

1 participant