From a092430bc22c4f8ba3f67adaa86876d30f721b39 Mon Sep 17 00:00:00 2001 From: nadav78 Date: Thu, 26 Mar 2026 19:27:46 +0200 Subject: [PATCH] Fix ping instability by serializing async writes with a per-session write queue Multiple concurrent asio::async_write calls on the same socket are undefined behavior per ASIO docs. Under load (many alive players sending position packets), hundreds of async_write calls per second were outstanding on each player's socket simultaneously, causing the kernel to delay or reorder writes and back up pong (order 72) responses behind position broadcasts. Replace the fire-and-forget async_write in asyncWriteImpl with a queue: enqueue the serialized packet and start a write only when no write is already in progress. The completion handler pops the sent packet and starts the next one. This ensures exactly one outstanding async_write per session at all times. Co-Authored-By: Claude Sonnet 4.6 --- Common/include/Network/Session.h | 32 ++++++++++---------------------- Common/src/Network/Session.cpp | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Common/include/Network/Session.h b/Common/include/Network/Session.h index ede239ef..6afc66c1 100644 --- a/Common/include/Network/Session.h +++ b/Common/include/Network/Session.h @@ -53,6 +53,7 @@ namespace Common std::string m_ip; std::string m_gradedIp{ "" }; std::uint_least16_t m_port; + std::queue> m_writeQueue{}; public: std::string m_hwid = ""; @@ -142,24 +143,23 @@ namespace Common } void sendConnectionACK(Common::Enums::ServerType serverType); + void doWrite(); + template bool asyncWriteImpl(const Packet& message) requires (packetType == PacketType::ENCRYPTED || packetType == PacketType::UNECRYPTED) { if (!m_socket.is_open()) - { return false; - } - - auto packetData = std::make_shared>(); + std::vector packetData; if constexpr (packetType == PacketType::ENCRYPTED) { if (!message.isValidMain()) { return false; } - *packetData = message.generateOutgoingPacket(m_crypt.UserKey, m_crypt.isUsed); + packetData = message.generateOutgoingPacket(m_crypt.UserKey, m_crypt.isUsed); } else if constexpr (packetType == PacketType::UNECRYPTED) { @@ -167,25 +167,13 @@ namespace Common { return false; } - *packetData = message.generateOutgoingPacket(); + packetData = message.generateOutgoingPacket(); } - auto packetSize = message.getFullSize(); - - //if (m_crypt.isUsed) Common::Parser::parse(m_reader.data(), m_reader.size(), 13000, "client", "server", m_crypt.UserKey); - asio::async_write(m_socket, asio::buffer(packetData->data(), packetSize), - [this, self = this->shared_from_this(), packetData](const asio::error_code& errorCode, std::size_t) - { - if (!m_socket.is_open()) - { - return; - } - if (errorCode) - { - closeSocket(); - } - - }); + const bool idle = m_writeQueue.empty(); + m_writeQueue.push(std::move(packetData)); + if (idle) + doWrite(); return true; } diff --git a/Common/src/Network/Session.cpp b/Common/src/Network/Session.cpp index 075220c7..000e141f 100644 --- a/Common/src/Network/Session.cpp +++ b/Common/src/Network/Session.cpp @@ -149,7 +149,25 @@ namespace Common } } - void Session::closeSocket() + void Session::doWrite() + { + if (m_writeQueue.empty() || !m_socket.is_open()) + return; + + asio::async_write(m_socket, asio::buffer(m_writeQueue.front().data(), m_writeQueue.front().size()), + [this, self = this->shared_from_this()](const asio::error_code& errorCode, std::size_t) + { + m_writeQueue.pop(); + if (errorCode) + { + closeSocket(); + return; + } + doWrite(); + }); + } + + void Session::closeSocket() { if (!m_socket.is_open()) {