mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Don't block on hole punching when a client joins
CNetServer::SendHolePunchingMessage is called on the main thread (from the lobby's XMPP handler) whenever a lobby client requests to connect. It called StunClient::SendHolePunchingMessages, which sleeps for fw_punch.delay (default 200 ms) after each of the fw_punch.num_msg (default 3) messages. Freezing the main thread for ~600 ms freezes the hosting player's game, which in turn delays the lockstep turns of every player in the match. Instead, queue the request to the network server worker thread (like lobby auths) and pace the messages from CNetServerWorker::RunStep without sleeping. As the worker now owns the whole exchange, this also removes the concurrent use of the server's ENetHost from two threads. Punching stops as soon as the peer connects, which also gives num_msg = -1 (send indefinitely) a sane meaning. Fixes: #7957 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
917275d6cb
commit
0321f6a8a7
4 changed files with 92 additions and 7 deletions
|
|
@ -52,6 +52,7 @@
|
|||
#include "simulation2/system/TurnManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
|
|
@ -414,6 +415,7 @@ bool CNetServerWorker::RunStep()
|
|||
std::vector<bool> newStartGame;
|
||||
std::vector<std::pair<CStr, CStr>> newLobbyAuths;
|
||||
std::vector<u32> newTurnLength;
|
||||
std::vector<std::pair<CStr, u16>> newHolePunchRequests;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_WorkerMutex);
|
||||
|
|
@ -424,6 +426,7 @@ bool CNetServerWorker::RunStep()
|
|||
newStartGame.swap(m_StartGameQueue);
|
||||
newLobbyAuths.swap(m_LobbyAuthQueue);
|
||||
newTurnLength.swap(m_TurnLengthQueue);
|
||||
newHolePunchRequests.swap(m_HolePunchQueue);
|
||||
}
|
||||
|
||||
if (!newTurnLength.empty())
|
||||
|
|
@ -442,6 +445,8 @@ bool CNetServerWorker::RunStep()
|
|||
|
||||
CheckClientConnections();
|
||||
|
||||
ProcessHolePunching(std::move(newHolePunchRequests));
|
||||
|
||||
// Process network events:
|
||||
|
||||
ENetEvent event;
|
||||
|
|
@ -470,6 +475,13 @@ bool CNetServerWorker::RunStep()
|
|||
enet_address_get_host_ip(&event.peer->address, hostname, ARRAY_SIZE(hostname));
|
||||
LOGMESSAGE("Net server: Received connection from %s:%u", hostname, (unsigned int)event.peer->address.port);
|
||||
|
||||
// The peer connected, so the hole punching for it succeeded or wasn't needed.
|
||||
std::erase_if(m_HolePunchTargets, [&](const HolePunchTarget& target)
|
||||
{
|
||||
return target.address.host == event.peer->address.host &&
|
||||
target.address.port == event.peer->address.port;
|
||||
});
|
||||
|
||||
// Set up a session object for this peer
|
||||
|
||||
const std::unique_ptr<CNetServerSession>& session{m_Sessions.emplace_back(
|
||||
|
|
@ -1644,10 +1656,47 @@ CStrW CNetServerWorker::DeduplicatePlayerName(const CStrW& original)
|
|||
}
|
||||
}
|
||||
|
||||
void CNetServerWorker::SendHolePunchingMessage(const CStr& ipStr, u16 port)
|
||||
void CNetServerWorker::ProcessHolePunching(std::vector<std::pair<CStr, u16>>&& newRequests)
|
||||
{
|
||||
if (m_Host)
|
||||
StunClient::SendHolePunchingMessages(*m_Host, ipStr, port);
|
||||
if (!m_Host)
|
||||
return;
|
||||
|
||||
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
|
||||
for (const std::pair<CStr, u16>& request : newRequests)
|
||||
{
|
||||
// A negative number of messages means punching until the peer connects.
|
||||
const int numMessages{g_ConfigDB.Get("lobby.fw_punch.num_msg", 3)};
|
||||
if (numMessages == 0)
|
||||
continue;
|
||||
|
||||
ENetAddress address;
|
||||
address.port = request.second;
|
||||
if (enet_address_set_host(&address, request.first.c_str()) != 0)
|
||||
{
|
||||
LOGWARNING("Net server: Failed to resolve hole punching target %s", request.first.c_str());
|
||||
continue;
|
||||
}
|
||||
m_HolePunchTargets.push_back({address, numMessages, now});
|
||||
}
|
||||
|
||||
if (m_HolePunchTargets.empty())
|
||||
return;
|
||||
|
||||
const std::chrono::milliseconds delay{g_ConfigDB.Get("lobby.fw_punch.delay", 200)};
|
||||
for (HolePunchTarget& target : m_HolePunchTargets)
|
||||
{
|
||||
if (now < target.nextSendTime)
|
||||
continue;
|
||||
|
||||
StunClient::SendHolePunchingMessage(*m_Host, target.address);
|
||||
if (target.remainingMessages > 0)
|
||||
--target.remainingMessages;
|
||||
target.nextSendTime = now + delay;
|
||||
}
|
||||
|
||||
std::erase_if(m_HolePunchTargets,
|
||||
[](const HolePunchTarget& target) { return target.remainingMessages == 0; });
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1735,5 +1784,6 @@ void CNetServer::SetTurnLength(u32 msecs)
|
|||
|
||||
void CNetServer::SendHolePunchingMessage(const CStr& ip, u16 port)
|
||||
{
|
||||
m_Worker.SendHolePunchingMessage(ip, port);
|
||||
std::lock_guard<std::mutex> lock(m_Worker.m_WorkerMutex);
|
||||
m_Worker.m_HolePunchQueue.emplace_back(ip, port);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "network/NetHost.h"
|
||||
#include "ps/CStr.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <js/RootingAPI.h>
|
||||
#include <js/TypeDecls.h>
|
||||
|
|
@ -234,7 +235,11 @@ private:
|
|||
*/
|
||||
void CheckClientConnections();
|
||||
|
||||
void SendHolePunchingMessage(const CStr& ip, u16 port);
|
||||
/**
|
||||
* Turn new hole punching requests from the game thread into targets and
|
||||
* send the due hole punching messages without blocking.
|
||||
*/
|
||||
void ProcessHolePunching(std::vector<std::pair<CStr, u16>>&& newRequests);
|
||||
|
||||
/**
|
||||
* Internal script context for (de)serializing script messages,
|
||||
|
|
@ -319,6 +324,22 @@ private:
|
|||
*/
|
||||
std::time_t m_LastConnectionCheck{0};
|
||||
|
||||
/**
|
||||
* A peer which should receive hole punching messages until it connects.
|
||||
*/
|
||||
struct HolePunchTarget
|
||||
{
|
||||
ENetAddress address;
|
||||
/// A negative number means sending until the peer connects.
|
||||
int remainingMessages;
|
||||
std::chrono::steady_clock::time_point nextSendTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* The peers currently being sent hole punching messages.
|
||||
*/
|
||||
std::vector<HolePunchTarget> m_HolePunchTargets;
|
||||
|
||||
private:
|
||||
// Thread-related stuff:
|
||||
|
||||
|
|
@ -344,6 +365,7 @@ private:
|
|||
std::vector<bool> m_StartGameQueue;
|
||||
std::vector<std::pair<CStr, CStr>> m_LobbyAuthQueue;
|
||||
std::vector<u32> m_TurnLengthQueue;
|
||||
std::vector<std::pair<CStr, u16>> m_HolePunchQueue;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* Copyright (C) 2013-2016 SuperTuxKart-Team.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
|
|
@ -371,6 +371,11 @@ void SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAdd
|
|||
}
|
||||
}
|
||||
|
||||
void SendHolePunchingMessage(ENetHost& enetClient, const ENetAddress& addr)
|
||||
{
|
||||
SendStunRequest(enetClient, addr);
|
||||
}
|
||||
|
||||
bool FindLocalIP(CStr& ip)
|
||||
{
|
||||
// Open an UDP socket.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* Copyright (C) 2013-2016 SuperTuxKart-Team.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
|
|
@ -46,6 +46,14 @@ bool FindPublicIP(ENetHost& enetClient, CStr8& ip, u16& port);
|
|||
*/
|
||||
void SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAddress, u16 serverPort);
|
||||
|
||||
/**
|
||||
* Send a single hole punching message to the target address.
|
||||
* Unlike SendHolePunchingMessages this doesn't block, so the caller
|
||||
* is responsible for repeating the message at a sensible interval.
|
||||
* @see SendHolePunchingMessages
|
||||
*/
|
||||
void SendHolePunchingMessage(ENetHost& enetClient, const ENetAddress& addr);
|
||||
|
||||
/**
|
||||
* Return the local IP.
|
||||
* Technically not a STUN method, but convenient to define here.
|
||||
|
|
|
|||
Loading…
Reference in a new issue