From 4c5ca9bf32e5a9d569e83f66a439c65d8939a540 Mon Sep 17 00:00:00 2001 From: Martin Koller Date: Sat, 21 Jan 2017 12:11:14 +0100 Subject: handle mysql process crashes gracefully This patch checks if the mysqld stops unexpectedly when it was started from akonadiserver and tells the latter to quit when a stopped mysqld was discovered. Also in this case the local socket file is removed so that a restart can work without problem. REVIEW: 129264 --- src/server/storage/dbconfigmysql.cpp | 69 +++++++++++++++++++++++++++++++++--- src/server/storage/dbconfigmysql.h | 11 ++++-- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/server/storage/dbconfigmysql.cpp b/src/server/storage/dbconfigmysql.cpp index 0962ccb..7631890 100644 --- a/src/server/storage/dbconfigmysql.cpp +++ b/src/server/storage/dbconfigmysql.cpp @@ -32,6 +32,7 @@ #include #include #include +#include using namespace Akonadi; using namespace Akonadi::Server; @@ -190,6 +191,8 @@ bool DbConfigMysql::startInternalServer() const QString dataDir = StandardDirs::saveDir("data", QStringLiteral("db_data")); #ifndef Q_OS_WIN const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc"))); + const QString socketFile = QStringLiteral("%1/mysql.socket").arg(socketDirectory); + const QString pidFileName = QStringLiteral("%1/mysql.pid").arg(socketDirectory); #endif // generate config file @@ -289,6 +292,39 @@ bool DbConfigMysql::startInternalServer() qCCritical(AKONADISERVER_LOG) << "MySQL cannot deal with a socket path this long. Path was: " << socketDirectory; return false; } + + // If mysql.socket file exists, check if also the server process is still running, + // else we can safely remove the socket file (cleanup after a system crash, etc.) + QFile pidFile(pidFileName); + if (QFile::exists(socketFile) && pidFile.open(QIODevice::ReadOnly)) { + qCDebug(AKONADISERVER_LOG) << "Found a mysqld pid file, checking whether the server is still running..."; + QByteArray pid = pidFile.readLine().trimmed(); + QFile proc(QString::fromLatin1("/proc/" + pid + "/stat")); + // Check whether the process with the PID from pidfile still exists and whether + // it's actually still mysqld or, whether the PID has been recycled in the meanwhile. + bool serverIsRunning = false; + if (proc.open(QIODevice::ReadOnly)) { + const QByteArray stat = proc.readAll(); + const QList stats = stat.split(' '); + if (stats.count() > 1) { + // Make sure the PID actually belongs to mysql process + if (stats[1] == "(mysqld)") { + // Yup, our mysqld is actually running, so pretend we started the server + // and try to connect to it + qCWarning(AKONADISERVER_LOG) << "mysqld for Akonadi is already running, trying to connect to it."; + serverIsRunning = true; + } + } + proc.close(); + } + + if (!serverIsRunning) { + qCDebug(AKONADISERVER_LOG) << "No mysqld process with specified PID is running. Removing the pidfile and starting a new instance..."; + pidFile.close(); + pidFile.remove(); + QFile::remove(socketFile); + } + } #endif // synthesize the mysqld command @@ -296,14 +332,15 @@ bool DbConfigMysql::startInternalServer() arguments << QStringLiteral("--defaults-file=%1/mysql.conf").arg(akDir); arguments << QStringLiteral("--datadir=%1/").arg(dataDir); #ifndef Q_OS_WIN - arguments << QStringLiteral("--socket=%1/mysql.socket").arg(socketDirectory); + arguments << QStringLiteral("--socket=%1").arg(socketFile); + arguments << QStringLiteral("--pid-file=%1").arg(pidFileName); #else arguments << QString::fromLatin1("--shared-memory"); #endif // If mysql.socket file does not exists, then we must start the server, // otherwise we reconnect to it - if (!QFile::exists(QStringLiteral("%1/mysql.socket").arg(socketDirectory))) { + if (!QFile::exists(socketFile)) { // move mysql error log file out of the way const QFileInfo errorLog(dataDir + QDir::separator() + QLatin1String("mysql.err")); if (errorLog.exists()) { @@ -348,9 +385,11 @@ bool DbConfigMysql::startInternalServer() return false; } - #ifndef Q_OS_WIN + connect(mDatabaseProcess, static_cast(&QProcess::finished), + this, &DbConfigMysql::processFinished); + +#ifndef Q_OS_WIN // wait until mysqld has created the socket file (workaround for QTBUG-47475 in Qt5.5.0) - QString socketFile = QStringLiteral("%1/mysql.socket").arg(socketDirectory); int counter = 50; // avoid an endless loop in case mysqld terminated while ((counter-- > 0) && !QFileInfo::exists(socketFile)) { QThread::msleep(100); @@ -358,7 +397,6 @@ bool DbConfigMysql::startInternalServer() #endif } else { qCDebug(AKONADISERVER_LOG) << "Found mysql.socket file, reconnecting to the database"; - mDatabaseProcess = new QProcess(); } const QLatin1String initCon("initConnection"); @@ -378,7 +416,7 @@ bool DbConfigMysql::startInternalServer() if (opened) { break; } - if (mDatabaseProcess->waitForFinished(500)) { + if (mDatabaseProcess && mDatabaseProcess->waitForFinished(500)) { qCCritical(AKONADISERVER_LOG) << "Database process exited unexpectedly during initial connection!"; qCCritical(AKONADISERVER_LOG) << "executable:" << mMysqldPath; qCCritical(AKONADISERVER_LOG) << "arguments:" << arguments; @@ -459,12 +497,33 @@ bool DbConfigMysql::startInternalServer() return success; } +void DbConfigMysql::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + Q_UNUSED(exitCode); + Q_UNUSED(exitStatus); + + qCCritical(AKONADISERVER_LOG) << "database server stopped unexpectedly"; + +#ifndef Q_OS_WIN + // when the server stopped unexpectedly, make sure to remove the stale socket file since otherwise + // it can not be started again + const QString socketDirectory = Utils::preferredSocketDirectory(StandardDirs::saveDir("data", QStringLiteral("db_misc"))); + const QString socketFile = QStringLiteral("%1/mysql.socket").arg(socketDirectory); + QFile::remove(socketFile); +#endif + + QCoreApplication::quit(); +} + void DbConfigMysql::stopInternalServer() { if (!mDatabaseProcess) { return; } + disconnect(mDatabaseProcess, static_cast(&QProcess::finished), + this, &DbConfigMysql::processFinished); + // first, try the nicest approach if (!mCleanServerShutdownCommand.isEmpty()) { QProcess::execute(mCleanServerShutdownCommand); diff --git a/src/server/storage/dbconfigmysql.h b/src/server/storage/dbconfigmysql.h index 27841e8..a25f28b 100644 --- a/src/server/storage/dbconfigmysql.h +++ b/src/server/storage/dbconfigmysql.h @@ -21,14 +21,16 @@ #define DBCONFIGMYSQL_H #include "dbconfig.h" - -class QProcess; +#include +#include namespace Akonadi { namespace Server { -class DbConfigMysql : public DbConfig +class DbConfigMysql : public QObject, public DbConfig { + Q_OBJECT + public: DbConfigMysql(); @@ -75,6 +77,9 @@ public: /// reimpl void initSession(const QSqlDatabase &database) Q_DECL_OVERRIDE; +private Q_SLOTS: + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + private: int parseCommandLineToolsVersion() const; -- cgit v0.11.2