mirror of
https://git.revela.dev/itsRevela/LCE-Revelations.git
synced 2026-05-21 19:24:55 +00:00
fix: dedicated server thread safety, disconnect deadlock, and console freeze
- Protect PlayerList and ServerConnection players vectors with critical sections; all iterations use copy-on-read snapshots to prevent iterator invalidation during concurrent join/leave - Add null check on player bounding box in movement validation to prevent crash when player is removed mid-tick - Re-validate socket player pointer immediately before SendData to narrow the TOCTOU race window on disconnect - Replace inline disconnect cleanup with a queued system drained on the main tick thread, eliminating the done_cs -> m_playersCS lock inversion that caused deadlocks under load - Disable Windows QuickEdit mode at server startup to prevent console input selection from freezing the process - Move chunk priority sort behind ServerConnection::sortPlayersByChunkPriority() to keep the players vector lock-protected
This commit is contained in:
@@ -2401,36 +2401,7 @@ void MinecraftServer::chunkPacketManagement_PreTick()
|
||||
s_tickStartTime = System::currentTimeMillis();
|
||||
s_sentTo.clear();
|
||||
|
||||
vector< shared_ptr<PlayerConnection> > *players = connection->getPlayers();
|
||||
|
||||
if( players->size() )
|
||||
{
|
||||
vector< shared_ptr<PlayerConnection> > playersOrig = *players;
|
||||
players->clear();
|
||||
|
||||
do
|
||||
{
|
||||
int longestTime = 0;
|
||||
auto playerConnectionBest = playersOrig.begin();
|
||||
for( auto it = playersOrig.begin(); it != playersOrig.end(); it++)
|
||||
{
|
||||
int thisTime = 0;
|
||||
INetworkPlayer *np = (*it)->getNetworkPlayer();
|
||||
if( np )
|
||||
{
|
||||
thisTime = np->GetTimeSinceLastChunkPacket_ms();
|
||||
}
|
||||
|
||||
if( thisTime > longestTime )
|
||||
{
|
||||
playerConnectionBest = it;
|
||||
longestTime = thisTime;
|
||||
}
|
||||
}
|
||||
players->push_back(*playerConnectionBest);
|
||||
playersOrig.erase(playerConnectionBest);
|
||||
} while ( playersOrig.size() > 0 );
|
||||
}
|
||||
connection->sortPlayersByChunkPriority();
|
||||
}
|
||||
|
||||
void MinecraftServer::chunkPacketManagement_PostTick()
|
||||
|
||||
@@ -205,31 +205,14 @@ void PlayerConnection::disconnect(DisconnectPacket::eDisconnectReason reason)
|
||||
app.DebugPrintf("PlayerConnection disconect reason: %d\n", reason );
|
||||
player->disconnect();
|
||||
|
||||
// 4J Stu - Need to remove the player from the receiving list before their socket is NULLed so that we can find another player on their system
|
||||
server->getPlayers()->removePlayerFromReceiving( player );
|
||||
send(std::make_shared<DisconnectPacket>(reason));
|
||||
connection->sendAndQuit();
|
||||
// 4J-PB - removed, since it needs to be localised in the language the client is in
|
||||
//server->players->broadcastAll( shared_ptr<ChatPacket>( new ChatPacket(L"<22>e" + player->name + L" left the game.") ) );
|
||||
if (!kickLeaveMessage.empty())
|
||||
{
|
||||
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(kickLeaveMessage));
|
||||
}
|
||||
else if (!fourKitHandledQuit)
|
||||
{
|
||||
if(getWasKicked())
|
||||
{
|
||||
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerKickedFromGame));
|
||||
}
|
||||
else
|
||||
{
|
||||
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerLeftGame));
|
||||
}
|
||||
}
|
||||
|
||||
server->getPlayers()->remove(player);
|
||||
// Mark done and release the lock BEFORE the heavy PlayerList work.
|
||||
// The actual removal, broadcast, and socket teardown are queued for
|
||||
// the next tick, which processes them without holding done_cs.
|
||||
done = true;
|
||||
LeaveCriticalSection(&done_cs);
|
||||
|
||||
server->getPlayers()->queueDisconnect(player, static_cast<int>(reason),
|
||||
kickLeaveMessage, getWasKicked(), fourKitHandledQuit);
|
||||
}
|
||||
|
||||
void PlayerConnection::handlePlayerInput(shared_ptr<PlayerInputPacket> packet)
|
||||
@@ -404,6 +387,7 @@ void PlayerConnection::handleMovePlayer(shared_ptr<MovePlayerPacket> packet)
|
||||
#endif
|
||||
|
||||
float r = 1 / 16.0f;
|
||||
if (player->bb == nullptr) return;
|
||||
bool oldOk = level->getCubes(player, player->bb->copy()->shrink(r, r, r))->empty();
|
||||
|
||||
if (player->onGround && !packet->onGround && yDist > 0)
|
||||
@@ -451,13 +435,19 @@ void PlayerConnection::handleMovePlayer(shared_ptr<MovePlayerPacket> packet)
|
||||
}
|
||||
player->absMoveTo(xt, yt, zt, yRotT, xRotT);
|
||||
|
||||
bool newOk = level->getCubes(player, player->bb->copy()->shrink(r, r, r))->empty();
|
||||
AABB *playerBB = player->bb;
|
||||
if (playerBB == nullptr)
|
||||
{
|
||||
teleport(xLastOk, yLastOk, zLastOk, yRotT, xRotT);
|
||||
return;
|
||||
}
|
||||
bool newOk = level->getCubes(player, playerBB->copy()->shrink(r, r, r))->empty();
|
||||
if (oldOk && (fail || !newOk) && !player->isSleeping())
|
||||
{
|
||||
teleport(xLastOk, yLastOk, zLastOk, yRotT, xRotT);
|
||||
return;
|
||||
}
|
||||
AABB *testBox = player->bb->copy()->grow(r, r, r)->expand(0, -0.55, 0);
|
||||
AABB *testBox = playerBB->copy()->grow(r, r, r)->expand(0, -0.55, 0);
|
||||
// && server.level.getCubes(player, testBox).size() == 0
|
||||
if (!server->isFlightAllowed() && !player->gameMode->isCreative() && !level->containsAnyBlocks(testBox) && !player->isAllowedToFly() )
|
||||
{
|
||||
@@ -843,23 +833,12 @@ void PlayerConnection::onDisconnect(DisconnectPacket::eDisconnectReason reason,
|
||||
false);
|
||||
fourKitHandledQuit = FourKitBridge::FirePlayerQuit(player->entityId);
|
||||
#endif
|
||||
// logger.info(player.name + " lost connection: " + reason);
|
||||
// 4J-PB - removed, since it needs to be localised in the language the client is in
|
||||
//server->players->broadcastAll( shared_ptr<ChatPacket>( new ChatPacket(L"<22>e" + player->name + L" left the game.") ) );
|
||||
if (!fourKitHandledQuit)
|
||||
{
|
||||
if(getWasKicked())
|
||||
{
|
||||
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerKickedFromGame));
|
||||
}
|
||||
else
|
||||
{
|
||||
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerLeftGame));
|
||||
}
|
||||
}
|
||||
server->getPlayers()->remove(player);
|
||||
|
||||
done = true;
|
||||
LeaveCriticalSection(&done_cs);
|
||||
|
||||
server->getPlayers()->queueDisconnect(player, static_cast<int>(reason),
|
||||
L"", getWasKicked(), fourKitHandledQuit);
|
||||
}
|
||||
|
||||
void PlayerConnection::openSecurityGate()
|
||||
|
||||
@@ -77,6 +77,8 @@ PlayerList::PlayerList(MinecraftServer *server)
|
||||
int rawMax = server->settings->getInt(L"max-players", 8);
|
||||
maxPlayers = static_cast<unsigned int>(Mth::clamp(rawMax, 1, MINECRAFT_NET_MAX_PLAYERS));
|
||||
doWhiteList = false;
|
||||
InitializeCriticalSection(&m_playersCS);
|
||||
InitializeCriticalSection(&m_disconnectCS);
|
||||
InitializeCriticalSection(&m_banCS);
|
||||
InitializeCriticalSection(&m_kickPlayersCS);
|
||||
InitializeCriticalSection(&m_closePlayersCS);
|
||||
@@ -91,11 +93,34 @@ PlayerList::~PlayerList()
|
||||
player->gameMode = nullptr;
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&m_playersCS);
|
||||
DeleteCriticalSection(&m_disconnectCS);
|
||||
DeleteCriticalSection(&m_banCS);
|
||||
DeleteCriticalSection(&m_kickPlayersCS);
|
||||
DeleteCriticalSection(&m_closePlayersCS);
|
||||
}
|
||||
|
||||
vector<shared_ptr<ServerPlayer> > PlayerList::getPlayersSnapshot()
|
||||
{
|
||||
EnterCriticalSection(&m_playersCS);
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = players;
|
||||
LeaveCriticalSection(&m_playersCS);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void PlayerList::queueDisconnect(shared_ptr<ServerPlayer> player, int reason, const wstring& kickMessage, bool wasKicked, bool fourKitHandledQuit)
|
||||
{
|
||||
PendingDisconnect pd;
|
||||
pd.player = player;
|
||||
pd.reason = reason;
|
||||
pd.kickMessage = kickMessage;
|
||||
pd.wasKicked = wasKicked;
|
||||
pd.fourKitHandledQuit = fourKitHandledQuit;
|
||||
EnterCriticalSection(&m_disconnectCS);
|
||||
m_pendingDisconnects.push_back(pd);
|
||||
LeaveCriticalSection(&m_disconnectCS);
|
||||
}
|
||||
|
||||
bool PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer> player, shared_ptr<LoginPacket> packet)
|
||||
{
|
||||
CompoundTag *playerTag = load(player);
|
||||
@@ -531,7 +556,9 @@ void PlayerList::add(shared_ptr<ServerPlayer> player)
|
||||
broadcastAll(std::make_shared<PlayerInfoPacket>(player));
|
||||
}
|
||||
|
||||
EnterCriticalSection(&m_playersCS);
|
||||
players.push_back(player);
|
||||
LeaveCriticalSection(&m_playersCS);
|
||||
|
||||
// 4J Added
|
||||
addPlayerToReceiving(player);
|
||||
@@ -548,13 +575,16 @@ void PlayerList::add(shared_ptr<ServerPlayer> player)
|
||||
changeDimension(player, nullptr);
|
||||
level->addEntity(player);
|
||||
|
||||
for (size_t i = 0; i < players.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> op = players.at(i);
|
||||
//player->connection->send(shared_ptr<PlayerInfoPacket>( new PlayerInfoPacket(op->name, true, op->latency) ) );
|
||||
if( op->connection->getNetworkPlayer() )
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (size_t i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
player->connection->send(std::make_shared<PlayerInfoPacket>(op));
|
||||
shared_ptr<ServerPlayer> op = snapshot.at(i);
|
||||
//player->connection->send(shared_ptr<PlayerInfoPacket>( new PlayerInfoPacket(op->name, true, op->latency) ) );
|
||||
if( op->connection->getNetworkPlayer() )
|
||||
{
|
||||
player->connection->send(std::make_shared<PlayerInfoPacket>(op));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,9 +610,10 @@ void PlayerList::add(shared_ptr<ServerPlayer> player)
|
||||
broadcastAll(std::make_shared<AddPlayerPacket>(player, xuid, onlineXuid, xp, yp, zp, yRotp, xRotp, yHeadRotp));
|
||||
|
||||
// Send all existing players to the new player
|
||||
for (size_t i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (size_t i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> op = players.at(i);
|
||||
shared_ptr<ServerPlayer> op = snapshot.at(i);
|
||||
if (op != player && op->connection->getNetworkPlayer())
|
||||
{
|
||||
PlayerUID opXuid = INVALID_XUID;
|
||||
@@ -605,9 +636,10 @@ void PlayerList::add(shared_ptr<ServerPlayer> player)
|
||||
if(level->isAtLeastOnePlayerSleeping())
|
||||
{
|
||||
shared_ptr<ServerPlayer> firstSleepingPlayer = nullptr;
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> thisPlayer = players[i];
|
||||
shared_ptr<ServerPlayer> thisPlayer = snapshot[i];
|
||||
if(thisPlayer->isSleeping())
|
||||
{
|
||||
if(firstSleepingPlayer == nullptr) firstSleepingPlayer = thisPlayer;
|
||||
@@ -655,11 +687,13 @@ if (player->riding != nullptr)
|
||||
level->getTracker()->removeEntity(player);
|
||||
level->removeEntity(player);
|
||||
level->getChunkMap()->remove(player);
|
||||
EnterCriticalSection(&m_playersCS);
|
||||
auto it = find(players.begin(), players.end(), player);
|
||||
if( it != players.end() )
|
||||
{
|
||||
players.erase(it);
|
||||
}
|
||||
LeaveCriticalSection(&m_playersCS);
|
||||
// Notify fork clients that this player has left the server so they can
|
||||
// clean up IQNet/Tab list entries. Uses a custom payload channel so the
|
||||
// wire format of existing packets is unchanged (upstream clients simply
|
||||
@@ -781,11 +815,13 @@ shared_ptr<ServerPlayer> PlayerList::respawn(shared_ptr<ServerPlayer> serverPlay
|
||||
}
|
||||
|
||||
serverPlayer->getLevel()->getChunkMap()->remove(serverPlayer);
|
||||
EnterCriticalSection(&m_playersCS);
|
||||
auto it = find(players.begin(), players.end(), serverPlayer);
|
||||
if( it != players.end() )
|
||||
{
|
||||
players.erase(it);
|
||||
}
|
||||
LeaveCriticalSection(&m_playersCS);
|
||||
server->getLevel(serverPlayer->dimension)->removeEntityImmediately(serverPlayer);
|
||||
|
||||
Pos *bedPosition = serverPlayer->getRespawnPosition();
|
||||
@@ -899,7 +935,9 @@ shared_ptr<ServerPlayer> PlayerList::respawn(shared_ptr<ServerPlayer> serverPlay
|
||||
|
||||
level->getChunkMap()->add(player);
|
||||
level->addEntity(player);
|
||||
EnterCriticalSection(&m_playersCS);
|
||||
players.push_back(player);
|
||||
LeaveCriticalSection(&m_playersCS);
|
||||
|
||||
player->initMenu();
|
||||
player->setHealth(player->getHealth());
|
||||
@@ -1135,10 +1173,11 @@ void PlayerList::tick()
|
||||
}
|
||||
|
||||
// Report cipher completion and open security gate for all completed handshakes
|
||||
vector<shared_ptr<ServerPlayer> > tickSnapshot = getPlayersSnapshot();
|
||||
for (unsigned char smallId : completed)
|
||||
{
|
||||
// Open the security gate -- flush buffered game packets now that cipher is active
|
||||
for (auto &p : players)
|
||||
for (auto &p : tickSnapshot)
|
||||
{
|
||||
if (p == nullptr || p->connection == nullptr) continue;
|
||||
INetworkPlayer *np = p->connection->getNetworkPlayer();
|
||||
@@ -1168,7 +1207,7 @@ void PlayerList::tick()
|
||||
for (unsigned char smallId : completed)
|
||||
{
|
||||
// Find the player by smallId
|
||||
for (auto &p : players)
|
||||
for (auto &p : tickSnapshot)
|
||||
{
|
||||
if (p == nullptr || p->connection == nullptr) continue;
|
||||
INetworkPlayer *np = p->connection->getNetworkPlayer();
|
||||
@@ -1212,7 +1251,7 @@ void PlayerList::tick()
|
||||
}
|
||||
|
||||
// Enforce identity token response timeout
|
||||
for (auto &p : players)
|
||||
for (auto &p : tickSnapshot)
|
||||
{
|
||||
if (p == nullptr || p->connection == nullptr) continue;
|
||||
int challengeTick = p->connection->getIdentityChallengeTick();
|
||||
@@ -1244,27 +1283,78 @@ void PlayerList::tick()
|
||||
sendAllPlayerInfoIn = 0;
|
||||
}
|
||||
|
||||
if (sendAllPlayerInfoIn < players.size())
|
||||
{
|
||||
shared_ptr<ServerPlayer> op = players[sendAllPlayerInfoIn];
|
||||
vector<shared_ptr<ServerPlayer> > infoSnapshot = getPlayersSnapshot();
|
||||
if (sendAllPlayerInfoIn < infoSnapshot.size())
|
||||
{
|
||||
shared_ptr<ServerPlayer> op = infoSnapshot[sendAllPlayerInfoIn];
|
||||
//broadcastAll(shared_ptr<PlayerInfoPacket>( new PlayerInfoPacket(op->name, true, op->latency) ) );
|
||||
if( op->connection->getNetworkPlayer() )
|
||||
{
|
||||
broadcastAll(std::make_shared<PlayerInfoPacket>(op));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnterCriticalSection(&m_closePlayersCS);
|
||||
while(!m_smallIdsToClose.empty())
|
||||
// Drain the pending disconnect queue. disconnect() enqueues here so it
|
||||
// can release done_cs before the heavy cleanup runs on the tick thread.
|
||||
{
|
||||
BYTE smallId = m_smallIdsToClose.front();
|
||||
m_smallIdsToClose.pop_front();
|
||||
std::deque<PendingDisconnect> dcCopy;
|
||||
EnterCriticalSection(&m_disconnectCS);
|
||||
dcCopy.swap(m_pendingDisconnects);
|
||||
LeaveCriticalSection(&m_disconnectCS);
|
||||
|
||||
while (!dcCopy.empty())
|
||||
{
|
||||
PendingDisconnect pd = dcCopy.front();
|
||||
dcCopy.pop_front();
|
||||
|
||||
server->getPlayers()->removePlayerFromReceiving(pd.player);
|
||||
if (pd.player->connection != nullptr)
|
||||
{
|
||||
pd.player->connection->send(std::make_shared<DisconnectPacket>(static_cast<DisconnectPacket::eDisconnectReason>(pd.reason)));
|
||||
pd.player->connection->connection->sendAndQuit();
|
||||
}
|
||||
|
||||
if (!pd.kickMessage.empty())
|
||||
{
|
||||
broadcastAll(std::make_shared<ChatPacket>(pd.kickMessage));
|
||||
}
|
||||
else if (!pd.fourKitHandledQuit)
|
||||
{
|
||||
if (pd.wasKicked)
|
||||
{
|
||||
broadcastAll(std::make_shared<ChatPacket>(pd.player->name, ChatPacket::e_ChatPlayerKickedFromGame));
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcastAll(std::make_shared<ChatPacket>(pd.player->name, ChatPacket::e_ChatPlayerLeftGame));
|
||||
}
|
||||
}
|
||||
|
||||
remove(pd.player);
|
||||
}
|
||||
}
|
||||
|
||||
// Drain the close queue: snapshot the deque, then release the CS before
|
||||
// calling disconnect() which may itself try to acquire other locks.
|
||||
std::deque<BYTE> closeCopy;
|
||||
EnterCriticalSection(&m_closePlayersCS);
|
||||
closeCopy.swap(m_smallIdsToClose);
|
||||
LeaveCriticalSection(&m_closePlayersCS);
|
||||
|
||||
{
|
||||
vector<shared_ptr<ServerPlayer> > closeSnapshot = getPlayersSnapshot();
|
||||
while(!closeCopy.empty())
|
||||
{
|
||||
BYTE smallId = closeCopy.front();
|
||||
closeCopy.pop_front();
|
||||
|
||||
shared_ptr<ServerPlayer> player = nullptr;
|
||||
|
||||
for(unsigned int i = 0; i < players.size(); i++)
|
||||
for(unsigned int i = 0; i < closeSnapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> p = players.at(i);
|
||||
shared_ptr<ServerPlayer> p = closeSnapshot.at(i);
|
||||
// 4J Stu - May be being a bit overprotective with all the nullptr checks, but adding late in TU7 so want to be safe
|
||||
if (p != nullptr && p->connection != nullptr && p->connection->connection != nullptr && p->connection->connection->getSocket() != nullptr && p->connection->connection->getSocket()->getSmallId() == smallId )
|
||||
{
|
||||
@@ -1286,13 +1376,19 @@ void PlayerList::tick()
|
||||
WinsockNetLayer::ClearSocketForSmallId(smallId);
|
||||
#endif
|
||||
}
|
||||
LeaveCriticalSection(&m_closePlayersCS);
|
||||
}
|
||||
|
||||
std::deque<BYTE> kickCopy;
|
||||
EnterCriticalSection(&m_kickPlayersCS);
|
||||
while(!m_smallIdsToKick.empty())
|
||||
kickCopy.swap(m_smallIdsToKick);
|
||||
LeaveCriticalSection(&m_kickPlayersCS);
|
||||
|
||||
{
|
||||
BYTE smallId = m_smallIdsToKick.front();
|
||||
m_smallIdsToKick.pop_front();
|
||||
vector<shared_ptr<ServerPlayer> > kickSnapshot = getPlayersSnapshot();
|
||||
while(!kickCopy.empty())
|
||||
{
|
||||
BYTE smallId = kickCopy.front();
|
||||
kickCopy.pop_front();
|
||||
INetworkPlayer *selectedPlayer = g_NetworkManager.GetPlayerBySmallId(smallId);
|
||||
if( selectedPlayer != nullptr )
|
||||
{
|
||||
@@ -1303,9 +1399,9 @@ void PlayerList::tick()
|
||||
// Kick this player from the game
|
||||
shared_ptr<ServerPlayer> player = nullptr;
|
||||
|
||||
for(unsigned int i = 0; i < players.size(); i++)
|
||||
for(unsigned int i = 0; i < kickSnapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> p = players.at(i);
|
||||
shared_ptr<ServerPlayer> p = kickSnapshot.at(i);
|
||||
PlayerUID playersXuid = p->getOnlineXuid();
|
||||
if (p != nullptr && ProfileManager.AreXUIDSEqual(playersXuid, xuid ) )
|
||||
{
|
||||
@@ -1326,7 +1422,7 @@ void PlayerList::tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&m_kickPlayersCS);
|
||||
}
|
||||
|
||||
// Check our receiving players, and if they are dead see if we can replace them
|
||||
for(unsigned int dim = 0; dim < 2; ++dim)
|
||||
@@ -1360,29 +1456,32 @@ void PlayerList::prioritiseTileChanges(int x, int y, int z, int dimension)
|
||||
|
||||
void PlayerList::broadcastAll(shared_ptr<Packet> packet)
|
||||
{
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> player = players[i];
|
||||
shared_ptr<ServerPlayer> player = snapshot[i];
|
||||
player->connection->send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerList::broadcastAll(shared_ptr<Packet> packet, int dimension)
|
||||
{
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> player = players[i];
|
||||
shared_ptr<ServerPlayer> player = snapshot[i];
|
||||
if (player->dimension == dimension) player->connection->send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
wstring PlayerList::getPlayerNames()
|
||||
{
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
wstring msg;
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
if (i > 0) msg += L", ";
|
||||
msg += players[i]->name;
|
||||
msg += snapshot[i]->name;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
@@ -1410,9 +1509,10 @@ bool PlayerList::isOp(shared_ptr<ServerPlayer> player)
|
||||
|
||||
shared_ptr<ServerPlayer> PlayerList::getPlayer(const wstring& name)
|
||||
{
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> p = players[i];
|
||||
shared_ptr<ServerPlayer> p = snapshot[i];
|
||||
if (p->name == name) // 4J - used to be case insensitive (using equalsIgnoreCase) - imagine we'll be shifting to XUIDs anyway
|
||||
{
|
||||
return p;
|
||||
@@ -1424,9 +1524,10 @@ shared_ptr<ServerPlayer> PlayerList::getPlayer(const wstring& name)
|
||||
// 4J Added
|
||||
shared_ptr<ServerPlayer> PlayerList::getPlayer(PlayerUID uid)
|
||||
{
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> p = players[i];
|
||||
shared_ptr<ServerPlayer> p = snapshot[i];
|
||||
if (p->getXuid() == uid || p->getOnlineXuid() == uid) // 4J - used to be case insensitive (using equalsIgnoreCase) - imagine we'll be shifting to XUIDs anyway
|
||||
{
|
||||
return p;
|
||||
@@ -1437,15 +1538,16 @@ shared_ptr<ServerPlayer> PlayerList::getPlayer(PlayerUID uid)
|
||||
|
||||
shared_ptr<ServerPlayer> PlayerList::getNearestPlayer(Pos *position, int range)
|
||||
{
|
||||
if (players.empty()) return nullptr;
|
||||
if (position == nullptr) return players.at(0);
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
if (snapshot.empty()) return nullptr;
|
||||
if (position == nullptr) return snapshot.at(0);
|
||||
shared_ptr<ServerPlayer> current = nullptr;
|
||||
double dist = -1;
|
||||
int rangeSqr = range * range;
|
||||
|
||||
for (size_t i = 0; i < players.size(); i++)
|
||||
for (size_t i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> next = players.at(i);
|
||||
shared_ptr<ServerPlayer> next = snapshot.at(i);
|
||||
double newDist = position->distSqr(next->getCommandSenderWorldPosition());
|
||||
|
||||
if ((dist == -1 || newDist < dist) && (range <= 0 || newDist <= rangeSqr))
|
||||
@@ -1566,9 +1668,10 @@ void PlayerList::broadcast(shared_ptr<Player> except, double x, double y, double
|
||||
sentTo.push_back(dynamic_pointer_cast<ServerPlayer>(except));
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<ServerPlayer> p = players[i];
|
||||
shared_ptr<ServerPlayer> p = snapshot[i];
|
||||
if (p == except) continue;
|
||||
if (p->dimension != dimension) continue;
|
||||
|
||||
@@ -1630,14 +1733,15 @@ void PlayerList::saveAll(ProgressListener *progressListener, bool bDeleteGuestMa
|
||||
if(playerIo)
|
||||
{
|
||||
playerIo->saveAllCachedData();
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
vector<shared_ptr<ServerPlayer> > snapshot = getPlayersSnapshot();
|
||||
for (unsigned int i = 0; i < snapshot.size(); i++)
|
||||
{
|
||||
playerIo->save(players[i]);
|
||||
playerIo->save(snapshot[i]);
|
||||
|
||||
//4J Stu - We don't want to save the map data for guests, so when we are sure that the player is gone delete the map
|
||||
if(bDeleteGuestMaps && players[i]->isGuest()) playerIo->deleteMapFilesForPlayer(players[i]);
|
||||
if(bDeleteGuestMaps && snapshot[i]->isGuest()) playerIo->deleteMapFilesForPlayer(snapshot[i]);
|
||||
|
||||
if(progressListener != nullptr) progressListener->progressStagePercentage((i * 100)/ static_cast<int>(players.size()));
|
||||
if(progressListener != nullptr) progressListener->progressStagePercentage((i * 100)/ static_cast<int>(snapshot.size()));
|
||||
}
|
||||
playerIo->clearOldPlayerFiles();
|
||||
playerIo->saveMapIdLookup();
|
||||
@@ -1794,9 +1898,15 @@ void PlayerList::removePlayerFromReceiving(shared_ptr<ServerPlayer> player, bool
|
||||
}
|
||||
|
||||
INetworkPlayer *thisPlayer = player->connection->getNetworkPlayer();
|
||||
|
||||
// Snapshot the players vector to avoid iterator invalidation during concurrent modifications
|
||||
EnterCriticalSection(&m_playersCS);
|
||||
vector<shared_ptr<ServerPlayer> > playersSnapshot = players;
|
||||
LeaveCriticalSection(&m_playersCS);
|
||||
|
||||
if( thisPlayer && playerRemoved )
|
||||
{
|
||||
for(auto& newPlayer : players)
|
||||
for(auto& newPlayer : playersSnapshot)
|
||||
{
|
||||
INetworkPlayer *otherPlayer = newPlayer->connection->getNetworkPlayer();
|
||||
|
||||
@@ -1821,7 +1931,7 @@ void PlayerList::removePlayerFromReceiving(shared_ptr<ServerPlayer> player, bool
|
||||
#endif
|
||||
// 4J Stu - Something went wrong, or possibly the QNet player left before we got here.
|
||||
// Re-check all active players and make sure they have someone on their system to receive all packets
|
||||
for(auto& newPlayer : players)
|
||||
for(auto& newPlayer : playersSnapshot)
|
||||
{
|
||||
INetworkPlayer *checkingPlayer = newPlayer->connection->getNetworkPlayer();
|
||||
|
||||
|
||||
@@ -24,11 +24,26 @@ private:
|
||||
// public static Logger logger = Logger.getLogger("Minecraft");
|
||||
public:
|
||||
vector<shared_ptr<ServerPlayer> > players;
|
||||
CRITICAL_SECTION m_playersCS; // Protects players vector for concurrent access
|
||||
vector<shared_ptr<ServerPlayer> > getPlayersSnapshot();
|
||||
|
||||
private:
|
||||
MinecraftServer *server;
|
||||
unsigned int maxPlayers;
|
||||
|
||||
// Pending disconnect queue: disconnect() enqueues here, tick() drains it.
|
||||
// This avoids holding done_cs across PlayerList operations (deadlock fix).
|
||||
struct PendingDisconnect
|
||||
{
|
||||
shared_ptr<ServerPlayer> player;
|
||||
int reason;
|
||||
wstring kickMessage;
|
||||
bool wasKicked;
|
||||
bool fourKitHandledQuit;
|
||||
};
|
||||
deque<PendingDisconnect> m_pendingDisconnects;
|
||||
CRITICAL_SECTION m_disconnectCS;
|
||||
|
||||
// 4J Added
|
||||
vector<PlayerUID> m_bannedXuids;
|
||||
CRITICAL_SECTION m_banCS; // 4J Added - protects m_bannedXuids for concurrent access
|
||||
@@ -82,6 +97,7 @@ public:
|
||||
void add(shared_ptr<ServerPlayer> player);
|
||||
void move(shared_ptr<ServerPlayer> player);
|
||||
void remove(shared_ptr<ServerPlayer> player);
|
||||
void queueDisconnect(shared_ptr<ServerPlayer> player, int reason, const wstring& kickMessage, bool wasKicked, bool fourKitHandledQuit);
|
||||
shared_ptr<ServerPlayer> getPlayerForLogin(PendingConnection *pendingConnection, const wstring& userName, PlayerUID xuid, PlayerUID OnlineXuid);
|
||||
shared_ptr<ServerPlayer> respawn(shared_ptr<ServerPlayer> serverPlayer, int targetDimension, bool keepAllPlayerData);
|
||||
void toggleDimension(shared_ptr<ServerPlayer> player, int targetDimension);
|
||||
|
||||
@@ -20,6 +20,7 @@ ServerConnection::ServerConnection(MinecraftServer *server)
|
||||
// 4J - added initialiser
|
||||
connectionCounter = 0;
|
||||
InitializeCriticalSection(&pending_cs);
|
||||
InitializeCriticalSection(&players_cs);
|
||||
|
||||
this->server = server;
|
||||
}
|
||||
@@ -27,6 +28,7 @@ ServerConnection::ServerConnection(MinecraftServer *server)
|
||||
ServerConnection::~ServerConnection()
|
||||
{
|
||||
DeleteCriticalSection(&pending_cs);
|
||||
DeleteCriticalSection(&players_cs);
|
||||
}
|
||||
|
||||
// 4J - added to handle incoming connections, to replace thread that original used to have
|
||||
@@ -38,7 +40,9 @@ void ServerConnection::NewIncomingSocket(Socket *socket)
|
||||
|
||||
void ServerConnection::addPlayerConnection(shared_ptr<PlayerConnection> uc)
|
||||
{
|
||||
EnterCriticalSection(&players_cs);
|
||||
players.push_back(uc);
|
||||
LeaveCriticalSection(&players_cs);
|
||||
}
|
||||
|
||||
void ServerConnection::handleConnection(shared_ptr<PendingConnection> uc)
|
||||
@@ -76,7 +80,9 @@ void ServerConnection::stop()
|
||||
}
|
||||
|
||||
// Snapshot to avoid iterator invalidation if disconnect modifies the vector.
|
||||
EnterCriticalSection(&players_cs);
|
||||
std::vector<shared_ptr<PlayerConnection> > playerSnapshot = players;
|
||||
LeaveCriticalSection(&players_cs);
|
||||
for (unsigned int i = 0; i < playerSnapshot.size(); i++)
|
||||
{
|
||||
shared_ptr<PlayerConnection> player = playerSnapshot[i];
|
||||
@@ -118,26 +124,34 @@ void ServerConnection::tick()
|
||||
}
|
||||
LeaveCriticalSection(&pending_cs);
|
||||
|
||||
for (unsigned int i = 0; i < players.size(); i++)
|
||||
{
|
||||
shared_ptr<PlayerConnection> player = players[i];
|
||||
shared_ptr<ServerPlayer> serverPlayer = player->getPlayer();
|
||||
if( serverPlayer )
|
||||
EnterCriticalSection(&players_cs);
|
||||
vector< shared_ptr<PlayerConnection> > tempPlayers = players;
|
||||
LeaveCriticalSection(&players_cs);
|
||||
|
||||
for (unsigned int i = 0; i < tempPlayers.size(); i++)
|
||||
{
|
||||
serverPlayer->updateFrameTick();
|
||||
serverPlayer->doChunkSendingTick(false);
|
||||
shared_ptr<PlayerConnection> player = tempPlayers[i];
|
||||
shared_ptr<ServerPlayer> serverPlayer = player->getPlayer();
|
||||
if( serverPlayer )
|
||||
{
|
||||
serverPlayer->updateFrameTick();
|
||||
serverPlayer->doChunkSendingTick(false);
|
||||
}
|
||||
player->tick();
|
||||
if (player->done)
|
||||
{
|
||||
EnterCriticalSection(&players_cs);
|
||||
auto it = find(players.begin(), players.end(), player);
|
||||
if (it != players.end()) players.erase(it);
|
||||
LeaveCriticalSection(&players_cs);
|
||||
}
|
||||
else
|
||||
{
|
||||
player->connection->flush();
|
||||
}
|
||||
}
|
||||
player->tick();
|
||||
if (player->done)
|
||||
{
|
||||
players.erase(players.begin()+i);
|
||||
i--;
|
||||
}
|
||||
else
|
||||
{
|
||||
player->connection->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,7 +177,10 @@ void ServerConnection::handleTextureReceived(const wstring &textureName)
|
||||
{
|
||||
m_pendingTextureRequests.erase(it);
|
||||
}
|
||||
for (auto& player : players)
|
||||
EnterCriticalSection(&players_cs);
|
||||
vector< shared_ptr<PlayerConnection> > tempPlayers = players;
|
||||
LeaveCriticalSection(&players_cs);
|
||||
for (auto& player : tempPlayers)
|
||||
{
|
||||
if (!player->done)
|
||||
{
|
||||
@@ -179,7 +196,10 @@ void ServerConnection::handleTextureAndGeometryReceived(const wstring &textureNa
|
||||
{
|
||||
m_pendingTextureRequests.erase(it);
|
||||
}
|
||||
for (auto& player : players)
|
||||
EnterCriticalSection(&players_cs);
|
||||
vector< shared_ptr<PlayerConnection> > tempPlayers = players;
|
||||
LeaveCriticalSection(&players_cs);
|
||||
for (auto& player : tempPlayers)
|
||||
{
|
||||
if (!player->done)
|
||||
{
|
||||
@@ -231,4 +251,46 @@ void ServerConnection::handleServerSettingsChanged(shared_ptr<ServerSettingsChan
|
||||
vector< shared_ptr<PlayerConnection> > * ServerConnection::getPlayers()
|
||||
{
|
||||
return &players;
|
||||
}
|
||||
|
||||
vector< shared_ptr<PlayerConnection> > ServerConnection::getPlayersSnapshot()
|
||||
{
|
||||
EnterCriticalSection(&players_cs);
|
||||
vector< shared_ptr<PlayerConnection> > snapshot = players;
|
||||
LeaveCriticalSection(&players_cs);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void ServerConnection::sortPlayersByChunkPriority()
|
||||
{
|
||||
EnterCriticalSection(&players_cs);
|
||||
if( players.size() )
|
||||
{
|
||||
vector< shared_ptr<PlayerConnection> > playersOrig = players;
|
||||
players.clear();
|
||||
|
||||
do
|
||||
{
|
||||
int longestTime = 0;
|
||||
auto playerConnectionBest = playersOrig.begin();
|
||||
for( auto it = playersOrig.begin(); it != playersOrig.end(); it++)
|
||||
{
|
||||
int thisTime = 0;
|
||||
INetworkPlayer *np = (*it)->getNetworkPlayer();
|
||||
if( np )
|
||||
{
|
||||
thisTime = np->GetTimeSinceLastChunkPacket_ms();
|
||||
}
|
||||
|
||||
if( thisTime > longestTime )
|
||||
{
|
||||
playerConnectionBest = it;
|
||||
longestTime = thisTime;
|
||||
}
|
||||
}
|
||||
players.push_back(*playerConnectionBest);
|
||||
playersOrig.erase(playerConnectionBest);
|
||||
} while ( playersOrig.size() > 0 );
|
||||
}
|
||||
LeaveCriticalSection(&players_cs);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ private:
|
||||
int connectionCounter;
|
||||
private:
|
||||
CRITICAL_SECTION pending_cs; // 4J added
|
||||
CRITICAL_SECTION players_cs; // Protects players vector for concurrent access
|
||||
vector< shared_ptr<PendingConnection> > pending;
|
||||
vector< shared_ptr<PlayerConnection> > players;
|
||||
|
||||
@@ -47,4 +48,6 @@ public:
|
||||
void handleTextureAndGeometryReceived(const wstring &textureName);
|
||||
void handleServerSettingsChanged(shared_ptr<ServerSettingsChangedPacket> packet);
|
||||
vector< shared_ptr<PlayerConnection> > *getPlayers();
|
||||
vector< shared_ptr<PlayerConnection> > getPlayersSnapshot();
|
||||
void sortPlayersByChunkPriority();
|
||||
};
|
||||
|
||||
@@ -371,6 +371,19 @@ int main(int argc, char **argv)
|
||||
config.showHelp = false;
|
||||
|
||||
SetConsoleCtrlHandler(ConsoleCtrlHandlerProc, TRUE);
|
||||
|
||||
// Disable QuickEdit mode so clicking in the console window doesn't freeze
|
||||
// the server process. Without this, any accidental click pauses all threads
|
||||
// that write to stdout until a key is pressed.
|
||||
{
|
||||
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD mode = 0;
|
||||
GetConsoleMode(hInput, &mode);
|
||||
mode &= ~ENABLE_QUICK_EDIT_MODE;
|
||||
mode |= ENABLE_EXTENDED_FLAGS;
|
||||
SetConsoleMode(hInput, mode);
|
||||
}
|
||||
|
||||
SetExeWorkingDirectory();
|
||||
|
||||
// Load base settings from server.properties, then override with CLI values when provided
|
||||
|
||||
@@ -506,7 +506,6 @@ void Socket::SocketOutputStreamNetwork::writeWithFlags(byteArray b, unsigned int
|
||||
INetworkPlayer *socketPlayer = m_socket->getPlayer();
|
||||
if(socketPlayer == nullptr)
|
||||
{
|
||||
app.DebugPrintf("Trying to write to network, but the socketPlayer is nullptr\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -518,30 +517,20 @@ void Socket::SocketOutputStreamNetwork::writeWithFlags(byteArray b, unsigned int
|
||||
bool requireAck = ( ( flags & NON_QNET_SENDDATA_ACK_REQUIRED ) == NON_QNET_SENDDATA_ACK_REQUIRED );
|
||||
#endif
|
||||
|
||||
// Re-validate the socket player immediately before use to minimize
|
||||
// the TOCTOU window where the network layer could remove the player
|
||||
// between our initial null-check and the SendData call.
|
||||
if( m_queueIdx == SOCKET_SERVER_END )
|
||||
{
|
||||
//printf( "Sent %u bytes of data from \"%ls\" to \"%ls\"\n",
|
||||
//buffer.dwDataSize,
|
||||
//hostPlayer->GetGamertag(),
|
||||
//m_socket->networkPlayer->GetGamertag());
|
||||
|
||||
hostPlayer->SendData(socketPlayer, buffer.pbyData, buffer.dwDataSize, lowPriority, requireAck);
|
||||
|
||||
// DWORD queueSize = hostPlayer->GetSendQueueSize( nullptr, QNET_GETSENDQUEUESIZE_BYTES );
|
||||
// if( queueSize > 24000 )
|
||||
// {
|
||||
// //printf("Queue size is: %d, forcing doWork()\n",queueSize);
|
||||
// g_NetworkManager.DoWork();
|
||||
// }
|
||||
INetworkPlayer *validatedPlayer = m_socket->getPlayer();
|
||||
if(validatedPlayer == nullptr) return;
|
||||
hostPlayer->SendData(validatedPlayer, buffer.pbyData, buffer.dwDataSize, lowPriority, requireAck);
|
||||
}
|
||||
else
|
||||
{
|
||||
//printf( "Sent %u bytes of data from \"%ls\" to \"%ls\"\n",
|
||||
//buffer.dwDataSize,
|
||||
//m_socket->networkPlayer->GetGamertag(),
|
||||
//hostPlayer->GetGamertag());
|
||||
|
||||
socketPlayer->SendData(hostPlayer, buffer.pbyData, buffer.dwDataSize, lowPriority, requireAck);
|
||||
INetworkPlayer *validatedPlayer = m_socket->getPlayer();
|
||||
if(validatedPlayer == nullptr) return;
|
||||
validatedPlayer->SendData(hostPlayer, buffer.pbyData, buffer.dwDataSize, lowPriority, requireAck);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@ This project is based on Legacy Console Edition v1.6.0560.0 (TU19) with fixes an
|
||||
|
||||
## Latest:
|
||||
|
||||
### Dedicated Server Stability Fixes
|
||||
|
||||
- Fixed crashes caused by concurrent access to the player list during multiplayer. The players vector is now protected by a critical section, and all iterations use copy-on-read snapshots to prevent iterator invalidation when players join or leave mid-tick
|
||||
- Fixed a null pointer crash in the movement validation handler when a player's bounding box was accessed during removal
|
||||
- Fixed a race condition in network socket writes where a disconnecting player's network reference could become invalid between validation and use
|
||||
- Fixed a deadlock in the player disconnect path by replacing inline cleanup with a queued disconnect system that is drained safely on the main tick thread
|
||||
- Disabled Windows QuickEdit mode on the server console to prevent the process from freezing until console input is received
|
||||
|
||||
### Beacon Menu Fixes
|
||||
|
||||
- Fixed the beacon consuming a payment item (emerald, diamond, iron ingot, or gold ingot) when pressing the submit checkmark without changing any powers
|
||||
|
||||
Reference in New Issue
Block a user