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).
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include "Log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <fmt/format.h>
|
||||
#include <random>
|
||||
#include <string>
|
||||
@@ -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 [email protected], 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<uint32>(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
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user