QtxAcqHistTracker: Implement complete algo; add depend graph
The dependency graph class enables us to perform analysis on the qutex acquisition history data. By generating the graph and detecting cycles in it, we can find true gridlocks. We use this graph analysis code to implement the algorithmically complete version of the gridlock detector.
This commit is contained in:
@@ -1,13 +1,177 @@
|
||||
#include "qutexAcquisitionHistoryTracker.h"
|
||||
#include "serializedAsynchronousContinuation.h"
|
||||
#include "qutex.h"
|
||||
#include "dependencyGraph.h"
|
||||
#include <memory>
|
||||
#include <forward_list>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace smo {
|
||||
|
||||
void DependencyGraph::addNode(const Node& node)
|
||||
{
|
||||
adjacencyList[node]; // Creates empty set if node doesn't exist
|
||||
}
|
||||
|
||||
void DependencyGraph::addEdge(const Node& source, const Node& target)
|
||||
{
|
||||
addNode(source);
|
||||
addNode(target);
|
||||
adjacencyList[source].insert(target);
|
||||
}
|
||||
|
||||
std::vector<std::vector<DependencyGraph::Node>>
|
||||
DependencyGraph::findCycles() const
|
||||
{
|
||||
std::unordered_set<Node> visited;
|
||||
std::unordered_set<Node> recursionStack;
|
||||
std::vector<std::vector<Node>> cycles;
|
||||
std::vector<Node> path;
|
||||
|
||||
for (const auto& entry : adjacencyList)
|
||||
{
|
||||
const Node& node = entry.first;
|
||||
if (visited.find(node) == visited.end()) {
|
||||
dfsCycleDetection(node, visited, recursionStack, path, cycles);
|
||||
}
|
||||
}
|
||||
|
||||
return cycles;
|
||||
}
|
||||
|
||||
bool DependencyGraph::hasCycles() const
|
||||
{
|
||||
std::unordered_set<Node> visited;
|
||||
std::unordered_set<Node> recursionStack;
|
||||
std::vector<std::vector<Node>> cycles;
|
||||
std::vector<Node> path;
|
||||
|
||||
for (const auto& entry : adjacencyList)
|
||||
{
|
||||
const Node& node = entry.first;
|
||||
if (visited.find(node) == visited.end())
|
||||
{
|
||||
dfsCycleDetection(node, visited, recursionStack, path, cycles);
|
||||
if (!cycles.empty())
|
||||
{ return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t DependencyGraph::getNodeCount() const
|
||||
{
|
||||
return adjacencyList.size();
|
||||
}
|
||||
|
||||
void DependencyGraph::dfsCycleDetection(
|
||||
const Node& node,
|
||||
std::unordered_set<Node>& visited,
|
||||
std::unordered_set<Node>& recursionStack,
|
||||
std::vector<Node>& path,
|
||||
std::vector<std::vector<Node>>& cycles
|
||||
)
|
||||
const
|
||||
{
|
||||
// Mark current node as visited and add to recursion stack
|
||||
visited.insert(node);
|
||||
recursionStack.insert(node);
|
||||
path.push_back(node);
|
||||
|
||||
// Check all adjacent nodes
|
||||
auto it = adjacencyList.find(node);
|
||||
if (it != adjacencyList.end())
|
||||
{
|
||||
for (const auto& adjacent : it->second)
|
||||
{
|
||||
// If adjacent node is in recursion stack, we found a cycle
|
||||
if (recursionStack.find(adjacent) != recursionStack.end())
|
||||
{
|
||||
// Find the start of the cycle in the current path
|
||||
auto cycleStart = std::find(path.begin(), path.end(), adjacent);
|
||||
if (cycleStart != path.end())
|
||||
{
|
||||
std::vector<Node> cycle(cycleStart, path.end());
|
||||
cycle.push_back(adjacent); // Complete the cycle
|
||||
cycles.push_back(cycle);
|
||||
}
|
||||
}
|
||||
// If adjacent node hasn't been visited, recurse
|
||||
else if (visited.find(adjacent) == visited.end())
|
||||
{
|
||||
dfsCycleDetection(
|
||||
adjacent, visited, recursionStack, path, cycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from recursion stack and path when backtracking
|
||||
recursionStack.erase(node);
|
||||
path.pop_back();
|
||||
}
|
||||
|
||||
// QutexAcquisitionHistoryTracker implementation
|
||||
std::unique_ptr<DependencyGraph>&&
|
||||
QutexAcquisitionHistoryTracker::generateGraph(bool dontAcquireLock)
|
||||
{
|
||||
auto graph = std::make_unique<DependencyGraph>();
|
||||
|
||||
if (!dontAcquireLock) {
|
||||
acquisitionHistoryLock.acquire();
|
||||
}
|
||||
|
||||
// First pass: Add all continuations as nodes
|
||||
for (const auto& entry : acquisitionHistory)
|
||||
{
|
||||
const auto& continuation = entry.first;
|
||||
graph->addNode(continuation);
|
||||
}
|
||||
|
||||
// Second pass: Add edges based on lock dependencies
|
||||
for (const auto& entry : acquisitionHistory)
|
||||
{
|
||||
const auto& continuation = entry.first;
|
||||
const auto& historyEntry = entry.second;
|
||||
const auto& wantedLock = historyEntry.first;
|
||||
const auto& heldLocks = historyEntry.second;
|
||||
|
||||
if (!heldLocks) { continue; }
|
||||
|
||||
// Check if any other continuation holds the lock this continuation wants
|
||||
for (const auto& otherEntry : acquisitionHistory)
|
||||
{
|
||||
const auto& otherContinuation = otherEntry.first;
|
||||
const auto& otherHistoryEntry = otherEntry.second;
|
||||
const auto& otherHeldLocks = otherHistoryEntry.second;
|
||||
|
||||
// Skip self-comparison
|
||||
if (continuation == otherContinuation) { continue; }
|
||||
if (!otherHeldLocks) { continue; }
|
||||
|
||||
// Check if other continuation holds the wanted lock
|
||||
for (const auto& otherHeldLock : *otherHeldLocks)
|
||||
{
|
||||
if (&otherHeldLock.get() == &wantedLock.get())
|
||||
{
|
||||
// Add edge: continuation -> otherContinuation
|
||||
// (continuation wants a lock held by otherContinuation)
|
||||
graph->addEdge(continuation, otherContinuation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dontAcquireLock) {
|
||||
acquisitionHistoryLock.release();
|
||||
}
|
||||
|
||||
return std::move(graph);
|
||||
}
|
||||
|
||||
/** EXPLANATION - GRIDLOCK DETECTION ALGORITHM:
|
||||
* This file implements gridlock detection algorithms that use a central
|
||||
* acquisition history to track all lockvokers suspected of being gridlocked.
|
||||
@@ -105,7 +269,8 @@ bool QutexAcquisitionHistoryTracker
|
||||
acquisitionHistoryLock.acquire();
|
||||
|
||||
// Iterate through all entries in the acquisition history
|
||||
for (const auto& entry : acquisitionHistory) {
|
||||
for (const auto& entry : acquisitionHistory)
|
||||
{
|
||||
const auto& continuation = entry.first;
|
||||
const auto& historyEntry = entry.second;
|
||||
|
||||
@@ -151,20 +316,78 @@ bool QutexAcquisitionHistoryTracker
|
||||
(void)firstFailedQutex;
|
||||
|
||||
/** ALGORITHMICALLY COMPLETE VERSION:
|
||||
* This function is intended to implement the algorithmically complete
|
||||
* version of gridlock detection that performs full circularity detection.
|
||||
* This would involve building a dependency graph from the acquisition
|
||||
* history and using graph traversal algorithms (such as DFS with cycle
|
||||
* detection) to identify true circular dependencies.
|
||||
* This function implements the algorithmically complete version of gridlock
|
||||
* detection that performs full circularity detection. It builds a dependency
|
||||
* graph from the acquisition history and uses DFS with cycle detection to
|
||||
* identify true circular dependencies.
|
||||
*
|
||||
* See the file-local comment above for the complete algorithm
|
||||
* explanation.
|
||||
* See the file-local comment above for the complete algorithm explanation.
|
||||
*/
|
||||
|
||||
// acquisitionHistoryLock.acquire();
|
||||
// TODO: Implement full circularity detection algorithm
|
||||
// acquisitionHistoryLock.release();
|
||||
return false;
|
||||
acquisitionHistoryLock.acquire();
|
||||
|
||||
// Helper function to print continuation dependency info
|
||||
auto printContinuationDependency = [&](
|
||||
const auto& fromContinuation, const auto& toContinuation
|
||||
)
|
||||
{
|
||||
auto it = acquisitionHistory.find(fromContinuation);
|
||||
if (it != acquisitionHistory.end())
|
||||
{
|
||||
const auto& wantedLock = it->second.first;
|
||||
std::cerr << " Continuation @" << fromContinuation.get()
|
||||
<< " wants lock[\"" << wantedLock.get().name << "\"], "
|
||||
<< "held by continuation @" << toContinuation.get()
|
||||
<< std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << " Continuation @" << fromContinuation.get()
|
||||
<< " -> continuation @" << toContinuation.get()
|
||||
<< std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
// Pass true to dontAcquireLock since we already hold it
|
||||
auto graph = generateGraph(true);
|
||||
|
||||
// Early return if no graph or no cycles
|
||||
if (!graph || !graph->hasCycles())
|
||||
{
|
||||
acquisitionHistoryLock.release();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cycles = graph->findCycles();
|
||||
|
||||
std::cerr << __func__ << ": CIRCULAR DEPENDENCIES DETECTED: Found "
|
||||
<< cycles.size() << " cycle(s) in lock dependency graph:" << std::endl;
|
||||
|
||||
for (size_t i = 0; i < cycles.size(); ++i)
|
||||
{
|
||||
const auto& cycle = cycles[i];
|
||||
std::cerr << " Cycle " << (i + 1) << ":\n";
|
||||
|
||||
for (size_t j = 0; j < cycle.size() - 1; ++j)
|
||||
{
|
||||
const auto& currentContinuation = cycle[j];
|
||||
const auto& nextContinuation = cycle[j + 1];
|
||||
printContinuationDependency(currentContinuation, nextContinuation);
|
||||
}
|
||||
|
||||
if (cycle.empty())
|
||||
{ continue; }
|
||||
|
||||
/* Handle the last edge (back to start of cycle)
|
||||
*/
|
||||
const auto& lastContinuation = cycle[cycle.size() - 1];
|
||||
const auto& firstContinuation = cycle[0];
|
||||
printContinuationDependency(lastContinuation, firstContinuation);
|
||||
}
|
||||
|
||||
acquisitionHistoryLock.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace smo
|
||||
|
||||
Reference in New Issue
Block a user