Skip to content

Server client network stablize - #4506

Open
KT053374 wants to merge 1 commit into
ACEmulator:masterfrom
KT053374:clean_stabilized_server_client_network
Open

KT053374 wants to merge 1 commit into
ACEmulator:masterfrom
KT053374:clean_stabilized_server_client_network

Conversation

@KT053374

Copy link
Copy Markdown
Contributor

Summary

While testing on the Asheron4Fun live server, we encountered repeated outbound network failures during heavy multibox activity. The issue presented as System.ArgumentException failures originating from MessageFragment.CreateServerFragment() during Session.TickOutbound(), along with related fragment-state messages indicating invalid positions and incorrect remaining-data calculations.

Problem

Investigation identified that multiple client sessions could process the same GameMessage while sharing its underlying MemoryStream.

In simple terms, the old code allowed several sessions to read from the same message while also sharing one movable stream position. One session could move that position while another session was still reading, causing the second session to read from the wrong location. Under enough concurrency, fragment boundaries could become inconsistent and eventually result in out-of-range reads and the exceptions observed in the logs.

Fix

The outbound message is now serialized and frozen once into a byte buffer when it enters the network queue. Each session then creates its fragments from fixed, read-only slices of that frozen payload instead of moving a shared MemoryStream cursor.

This removes the shared mutable stream position entirely, so one session can no longer interfere with another session's fragment reads.

The change also corrects an edge case in TailSize where messages whose payload length was an exact multiple of the 448-byte fragment size could incorrectly report the final fragment as header-only.

This change does not alter the packet format, fragment size, message serialization format, or checksum algorithm. It only changes how the already-serialized outbound message data is accessed during fragmentation.

The implementation also removes the temporary per-fragment payload copy by allowing ServerPacketFragment to operate directly on the read-only message slice. The primary purpose of this change is correctness and thread safety, with reduced fragment-allocation churn as a secondary benefit.

Regression tests were added for both the concurrency scenario and the exact-multiple fragment-size case.

Validation

The targeted network tests pass 4/4 with 0 failures.

The patched build was deployed to the Asheron4Fun live server on October 6, 2026 at 15:59:12 and remained under live production traffic through the end of the attached logs on October 12, 2026 at 10:38:42.

During that period, the patched server processed exactly 134,834 new network sessions, including same-IP multibox activity reaching at least 13 concurrent sessions, with no recurrence of:

  • MessageFragment.CreateServerFragment() exceptions
  • Session.TickOutbound() fragmentation failures
  • System.ArgumentException bounds errors
  • There is no data remaining
  • More data to send then data remaining!
  • Passed index computes to invalid position size

The changes in this PR are currently live on Asheron4Fun. Anyone who would like to test the implementation is welcome to log in as a client and exercise the server under normal or multibox usage.

The attached server-log archive contains approximately 5 days and 18 hours of post-patch live-server activity. For reviewers wishing to compare behavior before and after deployment, ACE_Log.txt.4 contains both sides of the transition:

  • Earlier in ACE_Log.txt.4, the original System.ArgumentException / MessageFragment.CreateServerFragment() failure is recorded repeatedly.
  • The patched server restart occurs in that same file at 2026-10-06 15:59:12.
  • Logs following that restart, continuing through ACE_Log.txt.3, .2, .1, and the current ACE_Log.txt, show the post-patch soak period with no recurrence of the original fragmentation failure.

The remaining rotated logs in the ZIP are retained as additional historical context.
ACE_Log.txt.zip

* Implement frozen data handling in GameMessage

Added synchronization for frozen data in GameMessage class.

* Refactor NetworkBundle for message queue and size

Changed messages queue to readonly and updated CurrentSize to long to prevent overflow.

* Refactor PacketFragment class for consistency

Make Length virtual so the server-side fragment can calculate its length without owning a separate byte[]

* Change parameter type from Span to ReadOnlySpan

* Refactor ServerPacketFragment to use ReadOnlyMemory

* Refactor MessageFragment to use ReadOnlyMemory for data

Refactor MessageFragment to use ReadOnlyMemory<byte> for data handling instead of MemoryStream. This change improves memory efficiency by avoiding per-fragment byte array allocations.

* Remove FrozenDataLength property from GameMessage

* Change CurrentSize type to int and update logic

* Clarify comments for fragment size constants

Updated comments for MaxFragmentSize and MaxFragmentDataSize constants to clarify their purpose.

* Remove payload validation in ServerPacketFragment

Removed validation checks for payload length in ServerPacketFragment constructor.

* Improve comments on MemoryStream initialization

Clarify MemoryStream initial capacity behavior in comments.

* Fix formatting in Enqueue method

* Add unit tests for ServerPacketFragment class

* Add unit tests for MessageFragment functionality

* Add test for concurrent fragmentation with frozen data

Added a test for concurrent fragmentation of game messages using frozen data.

* Change DataTestMethod to TestMethod for TailSize test
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