Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored how sql exec and prepare errors are handled in murmur #119

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
132 changes: 94 additions & 38 deletions src/murmur/ServerDB.cpp
Expand Up @@ -42,12 +42,9 @@
#include "ServerUser.h"
#include "User.h"

#define SQLDO(x) ServerDB::exec(query, QLatin1String(x), true)
#define SQLMAY(x) ServerDB::exec(query, QLatin1String(x), false, false)
#define SQLDO(x) ServerDB::exec(query, QLatin1String(x))
#define SQLPREP(x) ServerDB::prepare(query, QLatin1String(x))
#define SQLEXEC() ServerDB::exec(query)
#define SQLEXECBATCH() ServerDB::execBatch(query)
#define SOFTEXEC() ServerDB::exec(query, QString(), false)

class TransactionHolder {
public:
Expand Down Expand Up @@ -152,7 +149,7 @@ ServerDB::ServerDB() {
qWarning("Renaming old tables...");
SQLDO("ALTER TABLE `%1servers` RENAME TO `%1servers%2`");
if (version < 2)
SQLMAY("ALTER TABLE `%1log` RENAME TO `%1slog`");
SQLDO("ALTER TABLE `%1log` RENAME TO `%1slog`");
SQLDO("ALTER TABLE `%1slog` RENAME TO `%1slog%2`");
SQLDO("ALTER TABLE `%1config` RENAME TO `%1config%2`");
SQLDO("ALTER TABLE `%1channels` RENAME TO `%1channels%2`");
Expand Down Expand Up @@ -466,11 +463,7 @@ ServerDB::~ServerDB() {
db = NULL;
}

bool ServerDB::prepare(QSqlQuery &query, const QString &str, bool fatal, bool warn) {
if (! db->isValid()) {
qWarning("SQL [%s] rejected: Database is gone", qPrintable(str));
return false;
}
bool ServerDB::prepare(QSqlQuery &query, const QString &str, bool fatal) {
QString q;
if (str.contains(QLatin1String("%1"))) {
if (str.contains(QLatin1String("%2")))
Expand All @@ -483,58 +476,121 @@ bool ServerDB::prepare(QSqlQuery &query, const QString &str, bool fatal, bool wa

if (query.prepare(q)) {
return true;
} else {
db->close();
if (! db->open()) {
qFatal("Lost connection to SQL Database: Reconnect: %s", qPrintable(db->lastError().text()));
}
query = QSqlQuery();
if (query.prepare(q)) {
qWarning("SQL Connection lost, reconnection OK");
return true;
}
}

// reconnect and retry
if (!ServerDB::connectionFailure()) {
if (fatal) {
*db = QSqlDatabase();
qFatal("SQL Prepare Error [%s]: %s", qPrintable(q), qPrintable(query.lastError().text()));
} else if (warn) {
qDebug("SQL Prepare Error [%s]: %s", qPrintable(q), qPrintable(query.lastError().text()));
}
qWarning("SQL Prepare Error [%s]: %s", qPrintable(q), qPrintable(query.lastError().text()));
return false;
}

// Not a connection failure warn about it
qWarning("SQL Prepare Error [%s]: %s - retrying...", qPrintable(q), qPrintable(query.lastError().text()));

if (!ServerDB::reconnect()) {
if (fatal) {
qFatal("Failed to reconnect after SQL Prepare Error");
}
qWarning("Failed to reconnect after SQL Prepare Error");
return false;
}

query = QSqlQuery();
return ServerDB::prepare(query, str, fatal);
}

bool ServerDB::exec(QSqlQuery &query, const QString &str, bool fatal, bool warn) {
bool ServerDB::exec(QSqlQuery &query, const QString &str, bool fatal) {
if (! str.isEmpty())
prepare(query, str, fatal, warn);
prepare(query, str, fatal);
if (query.exec()) {
return true;
} else {
}

// query string not passed in so we can't retry
if (str.isEmpty() || !ServerDB::connectionFailure()) {
if (fatal) {
*db = QSqlDatabase();
qFatal("SQL Error [%s]: %s", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));
} else if (warn) {
qDebug("SQL Error [%s]: %s", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));
}
qWarning("SQL Error [%s]: %s", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));
return false;
}

// Not a connection failure warn about it
qWarning("SQL Error [%s]: %s - retrying...", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));

// reconnect and retry
if (!ServerDB::reconnect()) {
if (fatal) {
qFatal("Failed to reconnect after SQL Error");
}
qWarning("Failed to reconnect after SQL Error");
return false;
}
query = QSqlQuery();
return ServerDB::exec(query, str, fatal);
}

bool ServerDB::execBatch(QSqlQuery &query, const QString &str, bool fatal) {
if (! str.isEmpty())
prepare(query, str, fatal);
if (query.execBatch()) {
return true;
} else {

if (!query.execBatch()) {
if (fatal) {
*db = QSqlDatabase();
qFatal("SQL Error [%s]: %s", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));
} else
qDebug("SQL Error [%s]: %s", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));
qWarning("SQL Error [%s]: %s", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));
}
qWarning("SQL Error [%s]: %s", qPrintable(query.lastQuery()), qPrintable(query.lastError().text()));
return false;
}

return true;
}

bool ServerDB::connectionFailure() {
if (db->lastError().type() == QSqlError::NoError) {
return false;
}

// QT doesn't provide a driver independent way to detecting connection failures
// so we have to do things the hard way
if (Meta::mp.qsDBDriver != "QMYSQL") {
// Not implemented
return false;
}

// MySQL code
if (db->lastError().number() < 2000) {
// Server error assume connection is still up
return false;
}

// MySQL client error ( errno >= 2000 ) assume disconnect issue
qWarning("Database is gone? [%s] (%d)", qPrintable(db->lastError().text()), db->lastError().number());
return true;
}

bool ServerDB::reconnect(int retries) {
int retryInterval = 1;
if (0 == retries) {
// No retries requested
return false;
}

db->close(); // Ensure the db is closed cleanly
while (0 < retries) {
if (db->open()) {
return true;
}
retries--;
qWarning("Connection failed: %s, retry in %d secs - %d retries remaining...", qPrintable(db->lastError().text()), retryInterval, retries);
sleep(retryInterval);
}

qWarning("Reconnect failed: %s, no retries remaining", qPrintable(db->lastError().text()));
db->close(); // Ensure the db is closed cleanly

return false;
}

void Server::initialize() {
Expand Down Expand Up @@ -972,7 +1028,7 @@ bool Server::setInfo(int id, const QMap<int, QString> &setinfo) {
query.addBindValue(userids);
query.addBindValue(keys);
query.addBindValue(values);
SQLEXECBATCH();
ServerDB::execBatch(query, QString(), true);
}

return true;
Expand Down
8 changes: 5 additions & 3 deletions src/murmur/ServerDB.h
Expand Up @@ -63,9 +63,11 @@ class ServerDB {
static QList<LogRecord> getLog(int server_id, unsigned int offs_min, unsigned int offs_max);
static int getLogLen(int server_id);
static void wipeLogs();
static bool prepare(QSqlQuery &, const QString &, bool fatal = true, bool warn = true);
static bool exec(QSqlQuery &, const QString &str = QString(), bool fatal= true, bool warn = true);
static bool execBatch(QSqlQuery &, const QString &str = QString(), bool fatal= true);
static bool prepare(QSqlQuery &, const QString &, bool fatal = false);
static bool exec(QSqlQuery &, const QString &str = QString(), bool fatal = false);
static bool execBatch(QSqlQuery &, const QString &str = QString(), bool fatal = false);
static bool connectionFailure();
static bool reconnect(int retries = 20);
};

#endif