Fix memory corruption in PerformanceTimer when Stats are active

PerformanceTimer was neither thread safe nor re-entrant.  Because it was being used increasingly on render and worker threads it has increased the likelihood of heap corruption.
I was able to identify this by enabling full page heap verification using the gflags app.

https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/enable-page-heap
This commit is contained in:
Anthony J. Thibault 2018-08-07 18:03:03 -07:00
parent e9f23a43f6
commit c4c3581285
3 changed files with 25 additions and 9 deletions

View file

@ -431,7 +431,7 @@ void Stats::updateStats(bool force) {
// a new Map sorted by average time... // a new Map sorted by average time...
bool onlyDisplayTopTen = Menu::getInstance()->isOptionChecked(MenuOption::OnlyDisplayTopTen); bool onlyDisplayTopTen = Menu::getInstance()->isOptionChecked(MenuOption::OnlyDisplayTopTen);
QMap<float, QString> sortedRecords; QMap<float, QString> sortedRecords;
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords(); auto allRecords = PerformanceTimer::getAllTimerRecords();
QMapIterator<QString, PerformanceTimerRecord> i(allRecords); QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
while (i.hasNext()) { while (i.hasNext()) {
@ -479,7 +479,7 @@ void Stats::updateStats(bool force) {
bool operator<(const SortableStat& other) const { return priority < other.priority; } bool operator<(const SortableStat& other) const { return priority < other.priority; }
}; };
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords(); auto allRecords = PerformanceTimer::getAllTimerRecords();
std::priority_queue<SortableStat> idleUpdateStats; std::priority_queue<SortableStat> idleUpdateStats;
auto itr = allRecords.find("/idle/update"); auto itr = allRecords.find("/idle/update");
if (itr != allRecords.end()) { if (itr != allRecords.end()) {
@ -496,7 +496,7 @@ void Stats::updateStats(bool force) {
}; };
for (int32_t j = 0; j < categories.size(); ++j) { for (int32_t j = 0; j < categories.size(); ++j) {
QString recordKey = "/idle/update/" + categories[j]; QString recordKey = "/idle/update/" + categories[j];
auto record = PerformanceTimer::getTimerRecord(recordKey); auto& record = allRecords[recordKey];
if (record.getCount()) { if (record.getCount()) {
float dt = (float) record.getMovingAverage() / (float)USECS_PER_MSEC; float dt = (float) record.getMovingAverage() / (float)USECS_PER_MSEC;
QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt); QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt);

View file

@ -80,16 +80,19 @@ void PerformanceTimerRecord::tallyResult(const quint64& now) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
std::atomic<bool> PerformanceTimer::_isActive(false); std::atomic<bool> PerformanceTimer::_isActive(false);
std::mutex PerformanceTimer::_mutex;
QHash<QThread*, QString> PerformanceTimer::_fullNames; QHash<QThread*, QString> PerformanceTimer::_fullNames;
QMap<QString, PerformanceTimerRecord> PerformanceTimer::_records; QMap<QString, PerformanceTimerRecord> PerformanceTimer::_records;
PerformanceTimer::PerformanceTimer(const QString& name) { PerformanceTimer::PerformanceTimer(const QString& name) {
if (_isActive) { if (_isActive) {
_name = name; _name = name;
QString& fullName = _fullNames[QThread::currentThread()]; {
fullName.append("/"); std::lock_guard<std::mutex> guard(_mutex);
fullName.append(_name); QString& fullName = _fullNames[QThread::currentThread()];
fullName.append("/");
fullName.append(_name);
}
_start = usecTimestampNow(); _start = usecTimestampNow();
} }
} }
@ -97,6 +100,7 @@ PerformanceTimer::PerformanceTimer(const QString& name) {
PerformanceTimer::~PerformanceTimer() { PerformanceTimer::~PerformanceTimer() {
if (_isActive && _start != 0) { if (_isActive && _start != 0) {
quint64 elapsedUsec = (usecTimestampNow() - _start); quint64 elapsedUsec = (usecTimestampNow() - _start);
std::lock_guard<std::mutex> guard(_mutex);
QString& fullName = _fullNames[QThread::currentThread()]; QString& fullName = _fullNames[QThread::currentThread()];
PerformanceTimerRecord& namedRecord = _records[fullName]; PerformanceTimerRecord& namedRecord = _records[fullName];
namedRecord.accumulateResult(elapsedUsec); namedRecord.accumulateResult(elapsedUsec);
@ -111,11 +115,13 @@ bool PerformanceTimer::isActive() {
// static // static
QString PerformanceTimer::getContextName() { QString PerformanceTimer::getContextName() {
std::lock_guard<std::mutex> guard(_mutex);
return _fullNames[QThread::currentThread()]; return _fullNames[QThread::currentThread()];
} }
// static // static
void PerformanceTimer::addTimerRecord(const QString& fullName, quint64 elapsedUsec) { void PerformanceTimer::addTimerRecord(const QString& fullName, quint64 elapsedUsec) {
std::lock_guard<std::mutex> guard(_mutex);
PerformanceTimerRecord& namedRecord = _records[fullName]; PerformanceTimerRecord& namedRecord = _records[fullName];
namedRecord.accumulateResult(elapsedUsec); namedRecord.accumulateResult(elapsedUsec);
} }
@ -125,6 +131,7 @@ void PerformanceTimer::setActive(bool active) {
if (active != _isActive) { if (active != _isActive) {
_isActive.store(active); _isActive.store(active);
if (!active) { if (!active) {
std::lock_guard<std::mutex> guard(_mutex);
_fullNames.clear(); _fullNames.clear();
_records.clear(); _records.clear();
} }
@ -133,8 +140,15 @@ void PerformanceTimer::setActive(bool active) {
} }
} }
// static
QMap<QString, PerformanceTimerRecord> PerformanceTimer::getAllTimerRecords() {
std::lock_guard<std::mutex> guard(_mutex);
return _records;
};
// static // static
void PerformanceTimer::tallyAllTimerRecords() { void PerformanceTimer::tallyAllTimerRecords() {
std::lock_guard<std::mutex> guard(_mutex);
QMap<QString, PerformanceTimerRecord>::iterator recordsItr = _records.begin(); QMap<QString, PerformanceTimerRecord>::iterator recordsItr = _records.begin();
QMap<QString, PerformanceTimerRecord>::const_iterator recordsEnd = _records.end(); QMap<QString, PerformanceTimerRecord>::const_iterator recordsEnd = _records.end();
quint64 now = usecTimestampNow(); quint64 now = usecTimestampNow();
@ -150,6 +164,7 @@ void PerformanceTimer::tallyAllTimerRecords() {
} }
void PerformanceTimer::dumpAllTimerRecords() { void PerformanceTimer::dumpAllTimerRecords() {
std::lock_guard<std::mutex> guard(_mutex);
QMapIterator<QString, PerformanceTimerRecord> i(_records); QMapIterator<QString, PerformanceTimerRecord> i(_records);
while (i.hasNext()) { while (i.hasNext()) {
i.next(); i.next();

View file

@ -84,8 +84,7 @@ public:
static QString getContextName(); static QString getContextName();
static void addTimerRecord(const QString& fullName, quint64 elapsedUsec); static void addTimerRecord(const QString& fullName, quint64 elapsedUsec);
static const PerformanceTimerRecord& getTimerRecord(const QString& name) { return _records[name]; }; static QMap<QString, PerformanceTimerRecord> getAllTimerRecords();
static const QMap<QString, PerformanceTimerRecord>& getAllTimerRecords() { return _records; };
static void tallyAllTimerRecords(); static void tallyAllTimerRecords();
static void dumpAllTimerRecords(); static void dumpAllTimerRecords();
@ -93,6 +92,8 @@ private:
quint64 _start = 0; quint64 _start = 0;
QString _name; QString _name;
static std::atomic<bool> _isActive; static std::atomic<bool> _isActive;
static std::mutex _mutex; // used to guard multi-threaded access to _fullNames and _records
static QHash<QThread*, QString> _fullNames; static QHash<QThread*, QString> _fullNames;
static QMap<QString, PerformanceTimerRecord> _records; static QMap<QString, PerformanceTimerRecord> _records;
}; };