// // DeadlockWatchdog.h // interface/src // // Split from Application.cpp by HifiExperiments on 3/30/24 // Created by Andrzej Kapolka on 5/10/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #ifndef hifi_DeadlockWatchdog_h #define hifi_DeadlockWatchdog_h #include #include #include #include #include "InterfaceLogging.h" #include "SimpleMovingAverage.h" class DeadlockWatchdogThread : public QThread { public: static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; static const unsigned long MAX_HEARTBEAT_AGE_USECS = 120 * USECS_PER_SECOND; // 2 mins with no checkin probably a deadlock static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples // Set the heartbeat on launch DeadlockWatchdogThread() { setObjectName("Deadlock Watchdog"); // Give the heartbeat an initial value _heartbeat = usecTimestampNow(); _paused = false; connect(qApp, &QCoreApplication::aboutToQuit, [this] { _quit = true; }); } void setMainThreadID(Qt::HANDLE threadID) { _mainThreadID = threadID; } static void updateHeartbeat() { auto now = usecTimestampNow(); auto elapsed = now - _heartbeat; _movingAverage.addSample(elapsed); _heartbeat = now; } void deadlockDetectionCrash() { auto &ch = CrashHandler::getInstance(); ch.setAnnotation("_mod_faulting_tid", std::to_string((uint64_t)_mainThreadID)); ch.setAnnotation("deadlock", "1"); uint32_t* crashTrigger = nullptr; *crashTrigger = 0xDEAD10CC; } static void withPause(const std::function& lambda) { pause(); lambda(); resume(); } static void pause() { _paused = true; } static void resume() { // Update the heartbeat BEFORE resuming the checks updateHeartbeat(); _paused = false; } void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); // Don't do heartbeat detection under nsight if (_paused) { continue; } uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us uint64_t now = usecTimestampNow(); auto lastHeartbeatAge = (now > lastHeartbeat) ? now - lastHeartbeat : 0; auto elapsedMovingAverage = _movingAverage.getAverage(); if (elapsedMovingAverage > _maxElapsedAverage * 1.1f) { #if !defined(NDEBUG) qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "maxElapsed:" << _maxElapsed << "PREVIOUS maxElapsedAverage:" << _maxElapsedAverage << "NEW maxElapsedAverage:" << elapsedMovingAverage << "** NEW MAX ELAPSED AVERAGE **" << "samples:" << _movingAverage.getSamples(); #endif _maxElapsedAverage = elapsedMovingAverage; } if (lastHeartbeatAge > _maxElapsed) { #if !defined(NDEBUG) qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "PREVIOUS maxElapsed:" << _maxElapsed << "NEW maxElapsed:" << lastHeartbeatAge << "** NEW MAX ELAPSED **" << "maxElapsedAverage:" << _maxElapsedAverage << "samples:" << _movingAverage.getSamples(); #endif _maxElapsed = lastHeartbeatAge; } #if !defined(NDEBUG) if (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT) { qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge << "elapsedMovingAverage:" << elapsedMovingAverage << "** OVER EXPECTED VALUE **" << "maxElapsed:" << _maxElapsed << "maxElapsedAverage:" << _maxElapsedAverage << "samples:" << _movingAverage.getSamples(); } #endif if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) { qCDebug(interfaceapp_deadlock) << "DEADLOCK DETECTED -- " << "lastHeartbeatAge:" << lastHeartbeatAge << "[ lastHeartbeat :" << lastHeartbeat << "now:" << now << " ]" << "elapsedMovingAverage:" << elapsedMovingAverage << "maxElapsed:" << _maxElapsed << "maxElapsedAverage:" << _maxElapsedAverage << "samples:" << _movingAverage.getSamples(); // Don't actually crash in debug builds, in case this apparent deadlock is simply from // the developer actively debugging code #ifdef NDEBUG deadlockDetectionCrash(); #endif } } } static std::atomic _paused; static std::atomic _heartbeat; static std::atomic _maxElapsed; static std::atomic _maxElapsedAverage; static ThreadSafeMovingAverage _movingAverage; bool _quit { false }; Qt::HANDLE _mainThreadID = nullptr; }; #endif // hifi_DeadlockWatchdog_h