Fix for rare crash in LogHandler::flushRepeatedMessages()

This can happen when LogHandler::flushRepetedMessages is called on
the main thread, while the application is printing messages on
a separate thread.  The access to the _lastRepeatedMessage QHash was
not guarded.

I've added two mutexes to guard access to both the repeatedMessage
hashes/regexes and the onlyOnceMessages/regexes.  This will unfortunately
incur a performance hit for frequent debug logging, but that's better
then crashing.

Also, I've added the ability to print threadIDs as well as Process ids.
This is helpful when debugging multi-threaded access to shared variables.
This commit is contained in:
Anthony J. Thibault 2015-10-30 17:18:37 -07:00
parent 2a16b56cba
commit f50e1a0977
4 changed files with 60 additions and 36 deletions

View file

@ -74,7 +74,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
// make sure we output process IDs for a child AC otherwise it's insane to parse // make sure we output process IDs for a child AC otherwise it's insane to parse
LogHandler::getInstance().setShouldOutputPID(true); LogHandler::getInstance().setShouldOutputProcessID(true);
// setup our _requestAssignment member variable from the passed arguments // setup our _requestAssignment member variable from the passed arguments
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool); _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool);

View file

@ -44,7 +44,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME); LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME);
// make sure we output process IDs for a monitor otherwise it's insane to parse // make sure we output process IDs for a monitor otherwise it's insane to parse
LogHandler::getInstance().setShouldOutputPID(true); LogHandler::getInstance().setShouldOutputProcessID(true);
// create a NodeList so we can receive stats from children // create a NodeList so we can receive stats from children
DependencyManager::registerInheritance<LimitedNodeList, NodeList>(); DependencyManager::registerInheritance<LimitedNodeList, NodeList>();

View file

@ -12,9 +12,10 @@
#include <qcoreapplication.h> #include <qcoreapplication.h>
#include <qdatetime.h> #include <QDateTime>
#include <qdebug.h> #include <QDebug>
#include <qtimer.h> #include <QTimer>
#include <QThread>
#include "LogHandler.h" #include "LogHandler.h"
@ -24,13 +25,14 @@ LogHandler& LogHandler::getInstance() {
} }
LogHandler::LogHandler() : LogHandler::LogHandler() :
_shouldOutputPID(false) _shouldOutputProcessID(false),
_shouldOutputThreadID(false)
{ {
// setup our timer to flush the verbose logs every 5 seconds // setup our timer to flush the verbose logs every 5 seconds
QTimer* logFlushTimer = new QTimer(this); QTimer* logFlushTimer = new QTimer(this);
connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages); connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages);
logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000); logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000);
// when the log handler is first setup we should print our timezone // when the log handler is first setup we should print our timezone
QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t"); QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t");
printf("%s\n", qPrintable(timezoneString)); printf("%s\n", qPrintable(timezoneString));
@ -57,51 +59,55 @@ const char* stringForLogType(LogMsgType msgType) {
const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss"; const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss";
void LogHandler::flushRepeatedMessages() { void LogHandler::flushRepeatedMessages() {
QMutexLocker locker(&_repeatedMessageLock);
QHash<QString, int>::iterator message = _repeatMessageCountHash.begin(); QHash<QString, int>::iterator message = _repeatMessageCountHash.begin();
while (message != _repeatMessageCountHash.end()) { while (message != _repeatMessageCountHash.end()) {
if (message.value() > 0) { if (message.value() > 0) {
QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"") QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"")
.arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key())); .arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key()));
QMessageLogContext emptyContext; QMessageLogContext emptyContext;
printMessage(LogSuppressed, emptyContext, repeatMessage); printMessage(LogSuppressed, emptyContext, repeatMessage);
} }
_lastRepeatedMessage.remove(message.key()); _lastRepeatedMessage.remove(message.key());
message = _repeatMessageCountHash.erase(message); message = _repeatMessageCountHash.erase(message);
} }
} }
QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) { QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) {
if (message.isEmpty()) { if (message.isEmpty()) {
return QString(); return QString();
} }
if (type == LogDebug) { if (type == LogDebug) {
// for debug messages, check if this matches any of our regexes for repeated log messages // for debug messages, check if this matches any of our regexes for repeated log messages
QMutexLocker locker(&_repeatedMessageLock);
foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) { foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) {
QRegExp repeatRegex(regexString); QRegExp repeatRegex(regexString);
if (repeatRegex.indexIn(message) != -1) { if (repeatRegex.indexIn(message) != -1) {
if (!_repeatMessageCountHash.contains(regexString)) { if (!_repeatMessageCountHash.contains(regexString)) {
// we have a match but didn't have this yet - output the first one // we have a match but didn't have this yet - output the first one
_repeatMessageCountHash[regexString] = 0; _repeatMessageCountHash[regexString] = 0;
// break the foreach so we output the first match // break the foreach so we output the first match
break; break;
} else { } else {
// we have a match - add 1 to the count of repeats for this message and set this as the last repeated message // we have a match - add 1 to the count of repeats for this message and set this as the last repeated message
_repeatMessageCountHash[regexString] += 1; _repeatMessageCountHash[regexString] += 1;
_lastRepeatedMessage[regexString] = message; _lastRepeatedMessage[regexString] = message;
// return out, we're not printing this one // return out, we're not printing this one
return QString(); return QString();
} }
} }
} }
}
if (type == LogDebug) {
QMutexLocker locker(&_onlyOnceMessageLock);
// see if this message is one we should only print once // see if this message is one we should only print once
foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) { foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) {
QRegExp onlyOnceRegex(regexString); QRegExp onlyOnceRegex(regexString);
@ -118,23 +124,27 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
} }
} }
} }
// log prefix is in the following format // log prefix is in the following format
// [TIMESTAMP] [DEBUG] [PID] [TARGET] logged string // [TIMESTAMP] [DEBUG] [PID] [TID] [TARGET] logged string
QString prefixString = QString("[%1]").arg(QDateTime::currentDateTime().toString(DATE_STRING_FORMAT)); QString prefixString = QString("[%1]").arg(QDateTime::currentDateTime().toString(DATE_STRING_FORMAT));
prefixString.append(QString(" [%1]").arg(stringForLogType(type))); prefixString.append(QString(" [%1]").arg(stringForLogType(type)));
if (_shouldOutputPID) { if (_shouldOutputProcessID) {
prefixString.append(QString(" [%1]").arg(QCoreApplication::instance()->applicationPid())); prefixString.append(QString(" [%1]").arg(QCoreApplication::instance()->applicationPid()));
} }
if (_shouldOutputThreadID) {
size_t threadID = (size_t)QThread::currentThreadId();
prefixString.append(QString(" [%1]").arg(threadID));
}
if (!_targetName.isEmpty()) { if (!_targetName.isEmpty()) {
prefixString.append(QString(" [%1]").arg(_targetName)); prefixString.append(QString(" [%1]").arg(_targetName));
} }
QString logMessage = QString("%1 %2").arg(prefixString, message.split("\n").join("\n" + prefixString + " ")); QString logMessage = QString("%1 %2").arg(prefixString, message.split("\n").join("\n" + prefixString + " "));
fprintf(stdout, "%s\n", qPrintable(logMessage)); fprintf(stdout, "%s\n", qPrintable(logMessage));
return logMessage; return logMessage;
@ -143,3 +153,13 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
getInstance().printMessage((LogMsgType) type, context, message); getInstance().printMessage((LogMsgType) type, context, message);
} }
const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
QMutexLocker locker(&_repeatedMessageLock);
return *_repeatedMessageRegexes.insert(regexString);
}
const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) {
QMutexLocker locker(&_onlyOnceMessageLock);
return *_onlyOnceMessageRegexes.insert(regexString);
}

View file

@ -34,34 +34,38 @@ class LogHandler : public QObject {
Q_OBJECT Q_OBJECT
public: public:
static LogHandler& getInstance(); static LogHandler& getInstance();
/// sets the target name to output via the verboseMessageHandler, called once before logging begins /// sets the target name to output via the verboseMessageHandler, called once before logging begins
/// \param targetName the desired target name to output in logs /// \param targetName the desired target name to output in logs
void setTargetName(const QString& targetName) { _targetName = targetName; } void setTargetName(const QString& targetName) { _targetName = targetName; }
void setShouldOutputPID(bool shouldOutputPID) { _shouldOutputPID = shouldOutputPID; } void setShouldOutputProcessID(bool shouldOutputProcessID) { _shouldOutputProcessID = shouldOutputProcessID; }
void setShouldOutputThreadID(bool shouldOutputThreadID) { _shouldOutputThreadID = shouldOutputThreadID; }
QString printMessage(LogMsgType type, const QMessageLogContext& context, const QString &message); QString printMessage(LogMsgType type, const QMessageLogContext& context, const QString &message);
/// a qtMessageHandler that can be hooked up to a target that links to Qt /// a qtMessageHandler that can be hooked up to a target that links to Qt
/// prints various process, message type, and time information /// prints various process, message type, and time information
static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message); static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message);
const QString& addRepeatedMessageRegex(const QString& regexString) { return *_repeatedMessageRegexes.insert(regexString); } const QString& addRepeatedMessageRegex(const QString& regexString);
const QString& addOnlyOnceMessageRegex(const QString& regexString) { return *_onlyOnceMessageRegexes.insert(regexString); } const QString& addOnlyOnceMessageRegex(const QString& regexString);
private: private:
LogHandler(); LogHandler();
void flushRepeatedMessages(); void flushRepeatedMessages();
QString _targetName; QString _targetName;
bool _shouldOutputPID; bool _shouldOutputProcessID;
bool _shouldOutputThreadID;
QSet<QString> _repeatedMessageRegexes; QSet<QString> _repeatedMessageRegexes;
QHash<QString, int> _repeatMessageCountHash; QHash<QString, int> _repeatMessageCountHash;
QHash<QString, QString> _lastRepeatedMessage; QHash<QString, QString> _lastRepeatedMessage;
QMutex _repeatedMessageLock;
QSet<QString> _onlyOnceMessageRegexes; QSet<QString> _onlyOnceMessageRegexes;
QHash<QString, int> _onlyOnceMessageCountHash; QHash<QString, int> _onlyOnceMessageCountHash;
QMutex _onlyOnceMessageLock;
}; };
#endif // hifi_LogHandler_h #endif // hifi_LogHandler_h