From bbe7c54e3c84217e22a95d0a0aaa1901e4e5fd7e Mon Sep 17 00:00:00 2001 From: devbox Date: Wed, 12 Aug 2026 15:16:54 +1000 Subject: [PATCH] Fix pool-account starvation on fresh/renamed characters DBs Start the next PBV2_NNNN pool index past BOTH the realm-local counter and any PBV2_* bnet account already present in the shared auth DB, so a fresh or renamed characters DB no longer restarts numbering at 1 and collides with the existing fleet (AOR_NAME_ALREADY_EXIST x8 -> pool starvation). --- .../PlayerbotV2/Fleet/BotAccountMgr.cpp | 46 ++++++++++++++++++- src/modules/PlayerbotV2/Fleet/BotAccountMgr.h | 6 +++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/modules/PlayerbotV2/Fleet/BotAccountMgr.cpp b/src/modules/PlayerbotV2/Fleet/BotAccountMgr.cpp index 14b6dde5ff..11cbc5db55 100644 --- a/src/modules/PlayerbotV2/Fleet/BotAccountMgr.cpp +++ b/src/modules/PlayerbotV2/Fleet/BotAccountMgr.cpp @@ -6,6 +6,7 @@ #include "Log.h" #include +#include #include #include #include @@ -223,6 +224,37 @@ uint32 BotAccountMgr::next_pseudo_idx_locked() const return f[0].GetUInt32(); } +uint32 BotAccountMgr::max_pool_idx_in_auth() const +{ + // Pool accounts are keyed by a per-realm pseudo-id but live in the + // SHARED auth DB (bnet email PBV2_NNNN@playerbot-v2.local, stored + // uppercased by CreateBattlenetAccount). When a realm boots against a + // fresh/renamed characters DB, next_pseudo_idx_locked restarts at 1 + // while the auth DB still holds every PBV2_* account ever created — + // so account creation must jump past the max index that already + // exists in auth or every attempt collides and the pool starves. + auto r = LoginDatabase.PQuery( + "SELECT email FROM battlenet_accounts WHERE email LIKE 'PBV2\\_%'"); + if (!r) + return 0; + + uint32 maxIdx = 0; + do + { + const std::string email = r->Fetch()[0].GetString(); + const size_t at = email.find('@'); + if (at == std::string::npos) + continue; + const size_t us = email.rfind('_', at); + if (us == std::string::npos || us >= at) + continue; + const unsigned long val = std::strtoul(email.substr(us + 1, at - us - 1).c_str(), nullptr, 10); + maxIdx = std::max(maxIdx, static_cast(val)); + } while (r->NextRow()); + + return maxIdx; +} + uint32 BotAccountMgr::create_pool_account_locked() { // Each V2 pool account is created as a full Battle.net account with @@ -236,9 +268,19 @@ uint32 BotAccountMgr::create_pool_account_locked() // entry in `account` with bnetAccountId/bnetIndex set correctly. // Up to a few retries in case the chosen username collides with an // existing account (unlikely with the PBV2_ prefix but defensive). - for (int attempt = 0; attempt < 8; ++attempt) + // Index starts past BOTH the local counter and any PBV2_* account that + // already exists in the shared auth DB (a fresh/renamed characters DB + // restarts the local counter at 1 while auth retains the old fleet). + const uint32 localNext = next_pseudo_idx_locked(); + const uint32 authMaxIdx = max_pool_idx_in_auth(); + uint32 idx = std::max(localNext, authMaxIdx + 1); + if (idx != localNext) + TC_LOG_INFO("playerbot.v2", + "[BotAccountMgr] Pool index starts at {} (local next {}), skipping {} stale auth account(s).", + idx, localNext, idx - localNext); + + for (int attempt = 0; attempt < 8; ++attempt, ++idx) { - const uint32 idx = next_pseudo_idx_locked() + uint32(attempt); const std::string name = PoolAccountName(idx); const std::string password = GeneratePassword(); // The bnet "email" must look like a deliverable address; we use diff --git a/src/modules/PlayerbotV2/Fleet/BotAccountMgr.h b/src/modules/PlayerbotV2/Fleet/BotAccountMgr.h index 2bb4498aec..fb108a7dde 100644 --- a/src/modules/PlayerbotV2/Fleet/BotAccountMgr.h +++ b/src/modules/PlayerbotV2/Fleet/BotAccountMgr.h @@ -70,6 +70,12 @@ private: // Generates the next pseudo_account_idx (max + 1 from cache). uint32 next_pseudo_idx_locked() const; + // Maximum pool index (PBV2_NNNN) that already exists in the SHARED + // auth DB, or 0. The realm-local counter restarts when a realm uses a + // fresh/renamed characters DB, so creation must start above this to + // avoid colliding with accounts previous realms already provisioned. + uint32 max_pool_idx_in_auth() const; + // Synchronously creates a new pool account via sAccountMgr, inserts // the row in playerbot_v2_account, and returns its account id. 0 on // failure (e.g., name already exists in the legacy `account` table).