Skip to content

Commit

Permalink
[Bug Fix] Fix for undefined MySQL library behavior. (#2834)
Browse files Browse the repository at this point in the history
* MYSQL objects cannot be copied in a well defined way, this removes the copy and replaces it with another connection

* Change to share underlying pointers.

* Push up mutex changes

* Post rebase

* Formatting

---------

Co-authored-by: KimLS <KimLS@peqtgc.com>
Co-authored-by: Akkadius <akkadius1@gmail.com>
  • Loading branch information
3 people authored Feb 25, 2023
1 parent bad631d commit de2dfc1
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 61 deletions.
2 changes: 1 addition & 1 deletion client_files/export/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ int main(int argc, char **argv)
return 1;
}
} else {
content_db.SetMysql(database.getMySQL());
content_db.SetMySQL(database);
}

LogSys.SetDatabase(&database)
Expand Down
2 changes: 1 addition & 1 deletion client_files/import/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ int main(int argc, char **argv) {
return 1;
}
} else {
content_db.SetMysql(database.getMySQL());
content_db.SetMySQL(database);
}

LogSys.SetDatabase(&database)
Expand Down
90 changes: 44 additions & 46 deletions common/dbcore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@

DBcore::DBcore()
{
mysql_init(&mysql);
pHost = nullptr;
pUser = nullptr;
pPassword = nullptr;
pDatabase = nullptr;
pCompress = false;
pSSL = false;
pStatus = Closed;
mysql = mysql_init(nullptr);
mysqlOwner = true;
pHost = nullptr;
pUser = nullptr;
pPassword = nullptr;
pDatabase = nullptr;
pCompress = false;
pSSL = false;
pStatus = Closed;
m_mutex = new Mutex;
}

DBcore::~DBcore()
Expand All @@ -51,16 +53,10 @@ DBcore::~DBcore()
* are re-using the default database connection pointer when we dont have an
* external configuration setup ex: (content_database)
*/
std::string mysql_connection_host;
if (mysql.host) {
mysql_connection_host = mysql.host;
if (mysqlOwner) {
mysql_close(mysql);
}

if (GetOriginHost() != mysql_connection_host) {
return;
}

mysql_close(&mysql);
safe_delete_array(pHost);
safe_delete_array(pUser);
safe_delete_array(pPassword);
Expand All @@ -70,12 +66,12 @@ DBcore::~DBcore()
// Sends the MySQL server a keepalive
void DBcore::ping()
{
if (!MDatabase.trylock()) {
if (!m_mutex->trylock()) {
// well, if's it's locked, someone's using it. If someone's using it, it doesnt need a keepalive
return;
}
mysql_ping(&mysql);
MDatabase.unlock();
mysql_ping(mysql);
m_mutex->unlock();
}

MySQLRequestResult DBcore::QueryDatabase(std::string query, bool retryOnFailureOnce)
Expand All @@ -96,16 +92,16 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
BenchTimer timer;
timer.reset();

LockMutex lock(&MDatabase);
LockMutex lock(m_mutex);

// Reconnect if we are not connected before hand.
if (pStatus != Connected) {
Open();
}

// request query. != 0 indicates some kind of error.
if (mysql_real_query(&mysql, query, querylen) != 0) {
unsigned int errorNumber = mysql_errno(&mysql);
if (mysql_real_query(mysql, query, querylen) != 0) {
unsigned int errorNumber = mysql_errno(mysql);

if (errorNumber == CR_SERVER_GONE_ERROR) {
pStatus = Error;
Expand All @@ -129,26 +125,26 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo

auto errorBuffer = new char[MYSQL_ERRMSG_SIZE];

snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql));
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(mysql), mysql_error(mysql));

return MySQLRequestResult(nullptr, 0, 0, 0, 0, (uint32) mysql_errno(&mysql), errorBuffer);
return MySQLRequestResult(nullptr, 0, 0, 0, 0, (uint32) mysql_errno(mysql), errorBuffer);
}

auto errorBuffer = new char[MYSQL_ERRMSG_SIZE];
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql));
snprintf(errorBuffer, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(mysql), mysql_error(mysql));

/**
* Error logging
*/
if (mysql_errno(&mysql) > 0 && strlen(query) > 0) {
LogMySQLError("[{}] [{}]\n[{}]", mysql_errno(&mysql), mysql_error(&mysql), query);
if (mysql_errno(mysql) > 0 && strlen(query) > 0) {
LogMySQLError("[{}] [{}]\n[{}]", mysql_errno(mysql), mysql_error(mysql), query);
}

return MySQLRequestResult(nullptr, 0, 0, 0, 0, mysql_errno(&mysql), errorBuffer);
return MySQLRequestResult(nullptr, 0, 0, 0, 0, mysql_errno(mysql), errorBuffer);
}

// successful query. get results.
MYSQL_RES *res = mysql_store_result(&mysql);
MYSQL_RES *res = mysql_store_result(mysql);
uint32 rowCount = 0;

if (res != nullptr) {
Expand All @@ -157,10 +153,10 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo

MySQLRequestResult requestResult(
res,
(uint32) mysql_affected_rows(&mysql),
(uint32) mysql_affected_rows(mysql),
rowCount,
(uint32) mysql_field_count(&mysql),
(uint32) mysql_insert_id(&mysql)
(uint32) mysql_field_count(mysql),
(uint32) mysql_insert_id(mysql)
);

if (LogSys.log_settings[Logs::MySQLQuery].is_category_enabled == 1) {
Expand Down Expand Up @@ -206,7 +202,7 @@ uint32 DBcore::DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen)
{
// No good reason to lock the DB, we only need it in the first place to check char encoding.
// LockMutex lock(&MDatabase);
return mysql_real_escape_string(&mysql, tobuf, frombuf, fromlen);
return mysql_real_escape_string(mysql, tobuf, frombuf, fromlen);
}

bool DBcore::Open(
Expand All @@ -221,7 +217,7 @@ bool DBcore::Open(
bool iSSL
)
{
LockMutex lock(&MDatabase);
LockMutex lock(m_mutex);
safe_delete_array(pHost);
safe_delete_array(pUser);
safe_delete_array(pPassword);
Expand All @@ -241,13 +237,13 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
if (errbuf) {
errbuf[0] = 0;
}
LockMutex lock(&MDatabase);
LockMutex lock(m_mutex);
if (GetStatus() == Connected) {
return true;
}
if (GetStatus() == Error) {
mysql_close(&mysql);
mysql_init(&mysql); // Initialize structure again
mysql_close(mysql);
mysql_init(mysql); // Initialize structure again
}
if (!pHost) {
return false;
Expand All @@ -264,7 +260,7 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
if (pSSL) {
flags |= CLIENT_SSL;
}
if (mysql_real_connect(&mysql, pHost, pUser, pPassword, pDatabase, pPort, 0, flags)) {
if (mysql_real_connect(mysql, pHost, pUser, pPassword, pDatabase, pPort, 0, flags)) {
pStatus = Connected;

std::string connected_origin_host = pHost;
Expand All @@ -274,21 +270,16 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
}
else {
if (errnum) {
*errnum = mysql_errno(&mysql);
*errnum = mysql_errno(mysql);
}
if (errbuf) {
snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(&mysql), mysql_error(&mysql));
snprintf(errbuf, MYSQL_ERRMSG_SIZE, "#%i: %s", mysql_errno(mysql), mysql_error(mysql));
}
pStatus = Error;
return false;
}
}

void DBcore::SetMysql(MYSQL *mysql)
{
DBcore::mysql = *mysql;
}

const std::string &DBcore::GetOriginHost() const
{
return origin_host;
Expand All @@ -303,7 +294,14 @@ std::string DBcore::Escape(const std::string& s)
{
const std::size_t s_len = s.length();
std::vector<char> temp((s_len * 2) + 1, '\0');
mysql_real_escape_string(&mysql, temp.data(), s.c_str(), s_len);
mysql_real_escape_string(mysql, temp.data(), s.c_str(), s_len);

return temp.data();
}

void DBcore::SetMutex(Mutex *mutex)
{
safe_delete(m_mutex);

DBcore::m_mutex = mutex;
}
14 changes: 10 additions & 4 deletions common/dbcore.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,19 @@ class DBcore {
std::string Escape(const std::string& s);
uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen);
void ping();
MYSQL *getMySQL() { return &mysql; }
void SetMysql(MYSQL *mysql);

const std::string &GetOriginHost() const;
void SetOriginHost(const std::string &origin_host);

bool DoesTableExist(std::string table_name);

void SetMySQL(const DBcore &o)
{
mysql = o.mysql;
mysqlOwner = false;
}
void SetMutex(Mutex *mutex);

protected:
bool Open(
const char *iHost,
Expand All @@ -55,8 +60,9 @@ class DBcore {
private:
bool Open(uint32 *errnum = nullptr, char *errbuf = nullptr);

MYSQL mysql;
Mutex MDatabase;
MYSQL* mysql;
bool mysqlOwner;
Mutex *m_mutex;
eStatus pStatus;

std::mutex m_query_lock{};
Expand Down
2 changes: 1 addition & 1 deletion shared_memory/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ int main(int argc, char **argv)
return 1;
}
} else {
content_db.SetMysql(database.getMySQL());
content_db.SetMySQL(database);
}

LogSys.SetDatabase(&database)
Expand Down
73 changes: 73 additions & 0 deletions world/cli/database_concurrency.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <thread>
#include "../../common/repositories/zone_repository.h"
#include "../../common/eqemu_config.h"
#include <signal.h>

Database db;
Database db2;

volatile sig_atomic_t stop;
void inthand(int signum) {
stop = 1;
}

[[noreturn]] void DatabaseTest()
{
while (true) {
LogInfo("DatabaseTest Query");
db.QueryDatabase("SELECT 1");
}
}

[[noreturn]] void DatabaseTestSecondConnection()
{
while (true) {
LogInfo("DatabaseTest Query");
db2.QueryDatabase("SELECT 1");
}
}


void WorldserverCLI::TestDatabaseConcurrency(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Test command to test database concurrency";

if (cmd[{"-h", "--help"}]) {
return;
}

signal(SIGINT, inthand);

LogInfo("Database test");

auto mutex = new Mutex;

auto c = EQEmuConfig::get();
LogInfo("Connecting to MySQL");
if (!db.Connect(
c->DatabaseHost.c_str(),
c->DatabaseUsername.c_str(),
c->DatabasePassword.c_str(),
c->DatabaseDB.c_str(),
c->DatabasePort
)) {
LogError("Cannot continue without a database connection");
return;
}

db.SetMutex(mutex);

db2.SetMySQL(db);

db2.SetMutex(mutex);

std::thread(DatabaseTest).detach();
std::thread(DatabaseTest).detach();
std::thread(DatabaseTestSecondConnection).detach();

while (!stop) {

}

safe_delete(mutex);
}
2 changes: 2 additions & 0 deletions world/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,8 @@ int main(int argc, char **argv)
LogInfo("Signaling HTTP service to stop");
LogSys.CloseFileLogs();

WorldBoot::Shutdown();

return 0;
}

Expand Down
18 changes: 14 additions & 4 deletions world/world_boot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
extern ZSList zoneserver_list;
extern WorldConfig Config;

auto mutex = new Mutex;

void WorldBoot::GMSayHookCallBackProcessWorld(uint16 log_category, const char *func, std::string message)
{
// we don't want to loop up with chat messages
Expand Down Expand Up @@ -136,9 +138,7 @@ bool WorldBoot::LoadDatabaseConnections()
return false;
}

/**
* Multi-tenancy: Content database
*/
// Multi-tenancy - content database
if (!c->ContentDbHost.empty()) {
if (!content_db.Connect(
c->ContentDbHost.c_str(),
Expand All @@ -153,7 +153,12 @@ bool WorldBoot::LoadDatabaseConnections()
}
}
else {
content_db.SetMysql(database.getMySQL());
content_db.SetMySQL(database);
// when database and content_db share the same underlying mysql connection
// it needs to be protected by a shared mutex otherwise we produce concurrency issues
// when database actions are occurring in different threads
database.SetMutex(mutex);
content_db.SetMutex(mutex);
}

return true;
Expand Down Expand Up @@ -652,3 +657,8 @@ void WorldBoot::CheckForPossibleConfigurationIssues()
}
}

void WorldBoot::Shutdown()
{
safe_delete(mutex);
}

Loading

0 comments on commit de2dfc1

Please sign in to comment.