diff --git a/include/dependencyGraph.h b/include/dependencyGraph.h new file mode 100644 index 0000000..8433604 --- /dev/null +++ b/include/dependencyGraph.h @@ -0,0 +1,85 @@ +#ifndef DEPENDENCY_GRAPH_H +#define DEPENDENCY_GRAPH_H + +#include +#include +#include +#include + +namespace smo { + +// Forward declarations +class AsynchronousContinuationChainLink; + +/** + * @brief DependencyGraph - Represents a directed graph for lock dependency analysis + * + * This graph represents dependencies between continuations (lockvokers) where + * an edge from A to B means that continuation A wants a lock that is held by + * continuation B. This is used to detect circular dependencies (gridlocks). + */ +class DependencyGraph +{ +public: + typedef std::shared_ptr Node; + // Each node maps to a set of nodes it depends on + typedef std::unordered_map> AdjacencyList; + +public: + void addNode(const Node& node); + + /** + * @brief Add a directed edge from source to target + * @param source The continuation that wants a lock + * @param target The continuation that holds the wanted lock + */ + void addEdge(const Node& source, const Node& target); + + /** + * @brief Find all cycles in the graph using DFS + * @return Vector of cycles, where each cycle is a vector of nodes + */ + std::vector> findCycles() const; + + /** + * @brief Check if there are any cycles in the graph + * @return true if cycles exist, false otherwise + */ + bool hasCycles() const; + + /** + * @brief Get the number of nodes in the graph + * @return Number of nodes + */ + size_t getNodeCount() const; + + /** + * @brief Get the adjacency list for debugging + * @return Reference to the adjacency list + */ + const AdjacencyList& getAdjacencyList() const { return adjacencyList; } + +private: + /** + * @brief DFS helper for cycle detection + * @param node Current node being visited + * @param visited Set of nodes that have been fully processed + * @param recursionStack Set of nodes currently in the recursion stack + * @param path Current path being explored + * @param cycles Vector to store found cycles + */ + void dfsCycleDetection( + const Node& node, + std::unordered_set& visited, + std::unordered_set& recursionStack, + std::vector& path, + std::vector>& cycles) + const; + +private: + AdjacencyList adjacencyList; +}; + +} // namespace smo + +#endif // DEPENDENCY_GRAPH_H diff --git a/include/qutexAcquisitionHistoryTracker.h b/include/qutexAcquisitionHistoryTracker.h index 80246cc..8c52652 100644 --- a/include/qutexAcquisitionHistoryTracker.h +++ b/include/qutexAcquisitionHistoryTracker.h @@ -13,6 +13,7 @@ namespace smo { // Forward declarations class Qutex; class AsynchronousContinuationChainLink; +class DependencyGraph; /** * @brief QutexAcquisitionHistoryTracker - Tracks acquisition history for @@ -125,6 +126,16 @@ public: bool completelyTraceContinuationHistoryForGridlockOn( Qutex &firstFailedQutex); + /** + * @brief Generates a dependency graph among known continuations, based on + * the currently known acquisition history. There may well be a cyclical + * dependency which hasn't been reported to the history tracker yet. + * @param dontAcquireLock If true, skips acquiring the internal spinlock + * (assumes caller already holds it) + */ + [[nodiscard]] std::unique_ptr&& generateGraph( + bool dontAcquireLock = false); + // Disable copy constructor and assignment operator QutexAcquisitionHistoryTracker( const QutexAcquisitionHistoryTracker&) = delete; diff --git a/smocore/qutexAcquisitionHistoryTracker.cpp b/smocore/qutexAcquisitionHistoryTracker.cpp index beacefb..89f2990 100644 --- a/smocore/qutexAcquisitionHistoryTracker.cpp +++ b/smocore/qutexAcquisitionHistoryTracker.cpp @@ -1,13 +1,177 @@ #include "qutexAcquisitionHistoryTracker.h" #include "serializedAsynchronousContinuation.h" #include "qutex.h" +#include "dependencyGraph.h" #include #include #include #include +#include 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> +DependencyGraph::findCycles() const +{ + std::unordered_set visited; + std::unordered_set recursionStack; + std::vector> cycles; + std::vector 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 visited; + std::unordered_set recursionStack; + std::vector> cycles; + std::vector 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& visited, + std::unordered_set& recursionStack, + std::vector& path, + std::vector>& 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 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&& +QutexAcquisitionHistoryTracker::generateGraph(bool dontAcquireLock) +{ + auto graph = std::make_unique(); + + 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